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:
@ -41,7 +41,7 @@ export const SettingsNavbar = () => {
|
||||
end: false,
|
||||
});
|
||||
const isDevelopersSettingsActive = !!useMatch({
|
||||
path: useResolvedPath('/settings/api').pathname,
|
||||
path: useResolvedPath('/settings/developers/api-keys').pathname,
|
||||
end: true,
|
||||
});
|
||||
|
||||
@ -104,7 +104,7 @@ export const SettingsNavbar = () => {
|
||||
{isDevelopersSettingsEnabled && (
|
||||
<NavItem
|
||||
label="Developers"
|
||||
to="/settings/apis"
|
||||
to="/settings/developers/api-keys"
|
||||
Icon={IconRobot}
|
||||
active={isDevelopersSettingsActive}
|
||||
/>
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconCopy } from '@/ui/display/icon';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { beautifyDateDiff } from '~/utils/date-utils';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
`;
|
||||
|
||||
const StyledLinkContainer = styled.div`
|
||||
flex: 1;
|
||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type ApiKeyInputProps = { expiresAt?: string | null; apiKey: string };
|
||||
|
||||
export const ApiKeyInput = ({ expiresAt, apiKey }: ApiKeyInputProps) => {
|
||||
const theme = useTheme();
|
||||
const computeInfo = () => {
|
||||
if (!expiresAt) {
|
||||
return '';
|
||||
}
|
||||
return `This key will expire in ${beautifyDateDiff(expiresAt)}`;
|
||||
};
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledLinkContainer>
|
||||
<TextInput info={computeInfo()} value={apiKey} fullWidth />
|
||||
</StyledLinkContainer>
|
||||
<Button
|
||||
Icon={IconCopy}
|
||||
title="Copy"
|
||||
onClick={() => {
|
||||
enqueueSnackBar('Api Key copied to clipboard', {
|
||||
variant: 'success',
|
||||
icon: <IconCopy size={theme.icon.size.md} />,
|
||||
duration: 2000,
|
||||
});
|
||||
navigator.clipboard.writeText(apiKey);
|
||||
}}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -25,7 +25,7 @@ const StyledIconChevronRight = styled(IconChevronRight)`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
`;
|
||||
|
||||
export const SettingsApisFieldItemTableRow = ({
|
||||
export const SettingsApiKeysFieldItemTableRow = ({
|
||||
fieldItem,
|
||||
}: {
|
||||
fieldItem: ApisFiedlItem;
|
||||
@ -0,0 +1,19 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ApiKeyInput } from '@/settings/developers/components/ApiKeyInput';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
const meta: Meta<typeof ApiKeyInput> = {
|
||||
title: 'Pages/Settings/Developers/ApiKeys/ApiKeyInput',
|
||||
component: ApiKeyInput,
|
||||
decorators: [ComponentDecorator],
|
||||
args: {
|
||||
expiresAt: '2123-11-06T23:59:59.825Z',
|
||||
apiKey:
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0d2VudHktN2VkOWQyMTItMWMyNS00ZDAyLWJmMjUtNmFlY2NmN2VhNDE5IiwiaWF0IjoxNjk4MTQyODgyLCJleHAiOjE2OTk0MDE1OTksImp0aSI6ImMyMmFiNjQxLTVhOGYtNGQwMC1iMDkzLTk3MzUwYTM2YzZkOSJ9.JIe2TX5IXrdNl3n-kRFp3jyfNUE7unzXZLAzm2Gxl98',
|
||||
},
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ApiKeyInput>;
|
||||
|
||||
export const Default: Story = {};
|
||||
@ -0,0 +1,23 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { SettingsApiKeysFieldItemTableRow } from '@/settings/developers/components/SettingsApiKeysFieldItemTableRow';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
const meta: Meta<typeof SettingsApiKeysFieldItemTableRow> = {
|
||||
title: 'Pages/Settings/Developers/ApiKeys/SettingsApiKeysFieldItemTableRow',
|
||||
component: SettingsApiKeysFieldItemTableRow,
|
||||
decorators: [ComponentDecorator],
|
||||
args: {
|
||||
fieldItem: {
|
||||
id: '3f4a42e8-b81f-4f8c-9c20-1602e6b34791',
|
||||
name: 'Zapier Api Key',
|
||||
type: 'internal',
|
||||
expiration: 'In 3 days',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof SettingsApiKeysFieldItemTableRow>;
|
||||
|
||||
export const Default: Story = {};
|
||||
@ -0,0 +1,11 @@
|
||||
export const ExpirationDates: {
|
||||
value: number;
|
||||
label: string;
|
||||
}[] = [
|
||||
{ label: '15 days', value: 15 },
|
||||
{ label: '30 days', value: 30 },
|
||||
{ label: '90 days', value: 90 },
|
||||
{ label: '1 year', value: 365 },
|
||||
{ label: '2 years', value: 2 * 365 },
|
||||
{ label: 'Never', value: 10 * 365 },
|
||||
];
|
||||
@ -0,0 +1,9 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const DELETE_ONE_API_KEY = gql`
|
||||
mutation DeleteOneApiKey($apiKeyId: String!) {
|
||||
revokeOneApiKey(where: { id: $apiKeyId }) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const INSERT_ONE_API_KEY = gql`
|
||||
mutation InsertOneApiKey($data: ApiKeyCreateInput!) {
|
||||
createOneApiKey(data: $data) {
|
||||
id
|
||||
token
|
||||
expiresAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_API_KEY = gql`
|
||||
query GetApiKey($apiKeyId: String!) {
|
||||
findManyApiKey(where: { id: { equals: $apiKeyId } }) {
|
||||
id
|
||||
name
|
||||
expiresAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const generatedApiKeyState = atom<string | null | undefined>({
|
||||
key: 'generatedApiKeyState',
|
||||
default: null,
|
||||
});
|
||||
@ -36,7 +36,7 @@ export const NameFields = ({
|
||||
|
||||
const [updateUser] = useUpdateUserMutation();
|
||||
|
||||
// TODO: Enhance this with react-hook-form (https://www.react-hook-form.com)
|
||||
// TODO: Enhance this with react-web-hook-form (https://www.react-hook-form.com)
|
||||
const debouncedUpdate = debounce(async () => {
|
||||
if (onFirstNameUpdate) {
|
||||
onFirstNameUpdate(firstName);
|
||||
|
||||
@ -34,7 +34,7 @@ export const NameField = ({
|
||||
|
||||
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
||||
|
||||
// TODO: Enhance this with react-hook-form (https://www.react-hook-form.com)
|
||||
// TODO: Enhance this with react-web-hook-form (https://www.react-hook-form.com)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const debouncedUpdate = useCallback(
|
||||
debounce(async (name: string) => {
|
||||
|
||||
Reference in New Issue
Block a user