c28cba92e2
This allows entire components to be unmounted when the modal is hidden without affecting the fade in/out of the modal itself. This also makes it easier to programatically dismiss a modal without having to copy the visibility all over the place, and makes working with props much simpler in those modal components
117 lines
5.6 KiB
TypeScript
117 lines
5.6 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { Helmet } from 'react-helmet';
|
|
import ContentBox from '@/components/elements/ContentBox';
|
|
import CreateApiKeyForm from '@/components/dashboard/forms/CreateApiKeyForm';
|
|
import getApiKeys, { ApiKey } from '@/api/account/getApiKeys';
|
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
import { faKey, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
|
|
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
|
import deleteApiKey from '@/api/account/deleteApiKey';
|
|
import { Actions, useStoreActions, useStoreState } from 'easy-peasy';
|
|
import { ApplicationStore } from '@/state';
|
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
|
import { httpErrorToHuman } from '@/api/http';
|
|
import { format } from 'date-fns';
|
|
import PageContentBlock from '@/components/elements/PageContentBlock';
|
|
import tw from 'twin.macro';
|
|
import GreyRowBox from '@/components/elements/GreyRowBox';
|
|
|
|
export default () => {
|
|
const [ deleteIdentifier, setDeleteIdentifier ] = useState('');
|
|
const [ keys, setKeys ] = useState<ApiKey[]>([]);
|
|
const [ loading, setLoading ] = useState(true);
|
|
const { addError, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
|
const name = useStoreState((state: ApplicationStore) => state.settings.data!.name);
|
|
|
|
useEffect(() => {
|
|
clearFlashes('account');
|
|
getApiKeys()
|
|
.then(keys => setKeys(keys))
|
|
.then(() => setLoading(false))
|
|
.catch(error => {
|
|
console.error(error);
|
|
addError({ key: 'account', message: httpErrorToHuman(error) });
|
|
});
|
|
}, []);
|
|
|
|
const doDeletion = (identifier: string) => {
|
|
setLoading(true);
|
|
clearFlashes('account');
|
|
deleteApiKey(identifier)
|
|
.then(() => setKeys(s => ([
|
|
...(s || []).filter(key => key.identifier !== identifier),
|
|
])))
|
|
.catch(error => {
|
|
console.error(error);
|
|
addError({ key: 'account', message: httpErrorToHuman(error) });
|
|
})
|
|
.then(() => setLoading(false));
|
|
};
|
|
|
|
return (
|
|
<PageContentBlock>
|
|
<Helmet>
|
|
<title> {name} | API</title>
|
|
</Helmet>
|
|
<FlashMessageRender byKey={'account'} css={tw`mb-4`}/>
|
|
<div css={tw`flex`}>
|
|
<ContentBox title={'Create API Key'} css={tw`flex-1`}>
|
|
<CreateApiKeyForm onKeyCreated={key => setKeys(s => ([ ...s!, key ]))}/>
|
|
</ContentBox>
|
|
<ContentBox title={'API Keys'} css={tw`ml-10 flex-1`}>
|
|
<SpinnerOverlay visible={loading}/>
|
|
<ConfirmationModal
|
|
visible={!!deleteIdentifier}
|
|
title={'Confirm key deletion'}
|
|
buttonText={'Yes, delete key'}
|
|
onConfirmed={() => {
|
|
doDeletion(deleteIdentifier);
|
|
setDeleteIdentifier('');
|
|
}}
|
|
onModalDismissed={() => setDeleteIdentifier('')}
|
|
>
|
|
Are you sure you wish to delete this API key? All requests using it will immediately be
|
|
invalidated and will fail.
|
|
</ConfirmationModal>
|
|
{
|
|
keys.length === 0 ?
|
|
<p css={tw`text-center text-sm`}>
|
|
{loading ? 'Loading...' : 'No API keys exist for this account.'}
|
|
</p>
|
|
:
|
|
keys.map((key, index) => (
|
|
<GreyRowBox
|
|
key={key.identifier}
|
|
css={[ tw`bg-neutral-600 flex items-center`, index > 0 && tw`mt-2` ]}
|
|
>
|
|
<FontAwesomeIcon icon={faKey} css={tw`text-neutral-300`}/>
|
|
<div css={tw`ml-4 flex-1`}>
|
|
<p css={tw`text-sm`}>{key.description}</p>
|
|
<p css={tw`text-2xs text-neutral-300 uppercase`}>
|
|
Last used:
|
|
{key.lastUsedAt ? format(key.lastUsedAt, 'MMM do, yyyy HH:mm') : 'Never'}
|
|
</p>
|
|
</div>
|
|
<p css={tw`text-sm ml-4`}>
|
|
<code css={tw`font-mono py-1 px-2 bg-neutral-900 rounded`}>
|
|
{key.identifier}
|
|
</code>
|
|
</p>
|
|
<button
|
|
css={tw`ml-4 p-2 text-sm`}
|
|
onClick={() => setDeleteIdentifier(key.identifier)}
|
|
>
|
|
<FontAwesomeIcon
|
|
icon={faTrashAlt}
|
|
css={tw`text-neutral-400 hover:text-red-400 transition-colors duration-150`}
|
|
/>
|
|
</button>
|
|
</GreyRowBox>
|
|
))
|
|
}
|
|
</ContentBox>
|
|
</div>
|
|
</PageContentBlock>
|
|
);
|
|
};
|