2060 create a new api key (#2206)
* Add folder for api settings * Init create api key page * Update create api key page * Implement api call to create apiKey * Add create api key mutation * Get id when creating apiKey * Display created Api Key * Add delete api key button * Remove button from InputText * Update stuff * Add test for ApiDetail * Fix type * Use recoil instead of router state * Remane route paths * Remove online return * Move and test date util * Remove useless Component * Rename ApiKeys paths * Rename ApiKeys files * Add input text info testing * Rename hooks to webhooks * Remove console error * Add tests to reach minimum coverage
This commit is contained in:
@ -136,7 +136,7 @@ export const CreateProfile = () => {
|
||||
title="Name"
|
||||
description="Your name as it will be displayed on the app"
|
||||
/>
|
||||
{/* TODO: When react-hook-form is added to edit page we should create a dedicated component with context */}
|
||||
{/* TODO: When react-web-hook-form is added to edit page we should create a dedicated component with context */}
|
||||
<StyledComboInputContainer>
|
||||
<Controller
|
||||
name="firstName"
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
|
||||
import { SettingsApis } from '../SettingsApis';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Settings/SettingsApi',
|
||||
component: SettingsApis,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: '/settings/apis' },
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export type Story = StoryObj<typeof SettingsApis>;
|
||||
|
||||
export const Default: Story = {};
|
||||
@ -0,0 +1,76 @@
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
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 { 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,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const SettingsDevelopersApiKeyDetail = () => {
|
||||
const navigate = useNavigate();
|
||||
const { apiKeyId = '' } = useParams();
|
||||
const [generatedApiKey] = useRecoilState(generatedApiKeyState);
|
||||
const apiKeyQuery = useGetApiKeyQuery({
|
||||
variables: {
|
||||
apiKeyId,
|
||||
},
|
||||
});
|
||||
const [deleteApiKey] = useDeleteOneApiKeyMutation();
|
||||
const deleteIntegration = async () => {
|
||||
await deleteApiKey({ variables: { apiKeyId } });
|
||||
navigate('/settings/developers/api-keys');
|
||||
};
|
||||
const { expiresAt, name } = apiKeyQuery.data?.findManyApiKey[0] || {};
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
@ -2,7 +2,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { objectSettingsWidth } from '@/settings/data-model/constants/objectSettings';
|
||||
import { SettingsApisFieldItemTableRow } from '@/settings/developers/components/SettingsApisFieldItemTableRow';
|
||||
import { SettingsApiKeysFieldItemTableRow } from '@/settings/developers/components/SettingsApiKeysFieldItemTableRow';
|
||||
import { activeApiKeyItems } from '@/settings/developers/constants/mockObjects';
|
||||
import { IconPlus, IconSettings } from '@/ui/display/icon';
|
||||
import { H1Title } from '@/ui/display/typography/components/H1Title';
|
||||
@ -34,7 +34,7 @@ const StyledH1Title = styled(H1Title)`
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
export const SettingsApis = () => {
|
||||
export const SettingsDevelopersApiKeys = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
@ -48,7 +48,7 @@ export const SettingsApis = () => {
|
||||
accent="blue"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
navigate('/');
|
||||
navigate('/settings/developers/api-keys/new');
|
||||
}}
|
||||
/>
|
||||
</StyledHeader>
|
||||
@ -64,7 +64,7 @@ export const SettingsApis = () => {
|
||||
<TableHeader></TableHeader>
|
||||
</StyledTableRow>
|
||||
{activeApiKeyItems.map((fieldItem) => (
|
||||
<SettingsApisFieldItemTableRow
|
||||
<SettingsApiKeysFieldItemTableRow
|
||||
key={fieldItem.id}
|
||||
fieldItem={fieldItem}
|
||||
/>
|
||||
@ -0,0 +1,100 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
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 { IconSettings } from '@/ui/display/icon';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
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 { useInsertOneApiKeyMutation } from '~/generated/graphql';
|
||||
|
||||
export const SettingsDevelopersApiKeysNew = () => {
|
||||
const [insertOneApiKey] = useInsertOneApiKeyMutation();
|
||||
const navigate = useNavigate();
|
||||
const [, setGeneratedApiKey] = useRecoilState(generatedApiKeyState);
|
||||
const [formValues, setFormValues] = useState<{
|
||||
name: string;
|
||||
expirationDate: number;
|
||||
}>({
|
||||
expirationDate: ExpirationDates[0].value,
|
||||
name: '',
|
||||
});
|
||||
const onSave = async () => {
|
||||
const apiKey = await insertOneApiKey({
|
||||
variables: {
|
||||
data: {
|
||||
name: formValues.name,
|
||||
expiresAt: DateTime.now()
|
||||
.plus({ days: formValues.expirationDate })
|
||||
.toISODate(),
|
||||
},
|
||||
},
|
||||
});
|
||||
setGeneratedApiKey(apiKey.data?.createOneApiKey?.token);
|
||||
navigate(
|
||||
`/settings/developers/api-keys/${apiKey.data?.createOneApiKey?.id}`,
|
||||
);
|
||||
};
|
||||
const canSave = !!formValues.name;
|
||||
return (
|
||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||
<SettingsPageContainer>
|
||||
<SettingsHeaderContainer>
|
||||
<Breadcrumb
|
||||
links={[
|
||||
{ children: 'APIs', href: '/settings/developers/api-keys' },
|
||||
{ children: 'New' },
|
||||
]}
|
||||
/>
|
||||
<SaveAndCancelButtons
|
||||
isSaveDisabled={!canSave}
|
||||
onCancel={() => {
|
||||
navigate('/settings/developers/api-keys');
|
||||
}}
|
||||
onSave={onSave}
|
||||
/>
|
||||
</SettingsHeaderContainer>
|
||||
<Section>
|
||||
<H2Title title="Name" description="Name of your API key" />
|
||||
<TextInput
|
||||
placeholder="E.g. backoffice integration"
|
||||
value={formValues.name}
|
||||
onChange={(value) => {
|
||||
setFormValues((prevState) => ({
|
||||
...prevState,
|
||||
name: value,
|
||||
}));
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
</Section>
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Expiration Date"
|
||||
description="When the API key will expire."
|
||||
/>
|
||||
<Select
|
||||
dropdownScopeId="object-field-type-select"
|
||||
options={ExpirationDates}
|
||||
value={formValues.expirationDate}
|
||||
onChange={(value) => {
|
||||
setFormValues((prevState) => ({
|
||||
...prevState,
|
||||
expirationDate: value,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Section>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { SettingsDevelopersApiKeys } from '~/pages/settings/developers/api-keys/SettingsDevelopersApiKeys';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Settings/Developers/ApiKeys/SettingsDevelopersApiKeys',
|
||||
component: SettingsDevelopersApiKeys,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: '/settings/developers/api-keys' },
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export type Story = StoryObj<typeof SettingsDevelopersApiKeys>;
|
||||
|
||||
export const Default: Story = {
|
||||
play: async ({}) => {
|
||||
await sleep(100);
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,32 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { SettingsDevelopersApiKeyDetail } from '~/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail';
|
||||
import {
|
||||
PageDecorator,
|
||||
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> = {
|
||||
title: 'Pages/Settings/Developers/ApiKeys/SettingsDevelopersApiKeyDetail',
|
||||
component: SettingsDevelopersApiKeyDetail,
|
||||
decorators: [PageDecorator],
|
||||
args: {
|
||||
routePath: '/settings/apis/f7c6d736-8fcd-4e9c-ab99-28f6a9031570',
|
||||
state: mockedApiKeyToken,
|
||||
},
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
export default meta;
|
||||
|
||||
export type Story = StoryObj<typeof SettingsDevelopersApiKeyDetail>;
|
||||
|
||||
export const Default: Story = {
|
||||
play: async ({}) => {
|
||||
await sleep(100);
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { SettingsDevelopersApiKeysNew } from '~/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew';
|
||||
import {
|
||||
PageDecorator,
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Settings/Developers/ApiKeys/SettingsDevelopersApiKeysNew',
|
||||
component: SettingsDevelopersApiKeysNew,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: '/settings/developers/api-keys/new' },
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export type Story = StoryObj<typeof SettingsDevelopersApiKeysNew>;
|
||||
|
||||
export const Default: Story = {
|
||||
play: async ({}) => {
|
||||
await sleep(100);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user