123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- import styled from '@emotion/styled';
- import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
- import {Button} from 'sentry/components/button';
- import EmptyMessage from 'sentry/components/emptyMessage';
- import ExternalLink from 'sentry/components/links/externalLink';
- import Link from 'sentry/components/links/link';
- import LoadingError from 'sentry/components/loadingError';
- import LoadingIndicator from 'sentry/components/loadingIndicator';
- import Panel from 'sentry/components/panels/panel';
- import PanelBody from 'sentry/components/panels/panelBody';
- import PanelHeader from 'sentry/components/panels/panelHeader';
- import PanelItem from 'sentry/components/panels/panelItem';
- import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
- import {IconDelete} from 'sentry/icons';
- import {t, tct} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import type {ApiApplication} from 'sentry/types';
- import {setApiQueryData, useApiQuery, useQueryClient} from 'sentry/utils/queryClient';
- import useApi from 'sentry/utils/useApi';
- import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
- type Authorization = {
- application: ApiApplication;
- homepageUrl: string;
- id: string;
- scopes: string[];
- };
- function AccountAuthorizations() {
- const api = useApi();
- const queryClient = useQueryClient();
- const ENDPOINT = '/api-authorizations/';
- const {data, isLoading, isError, refetch} = useApiQuery<Authorization[]>([ENDPOINT], {
- staleTime: 0,
- });
- if (isLoading) {
- return <LoadingIndicator />;
- }
- if (isError) {
- return <LoadingError onRetry={refetch} />;
- }
- const handleRevoke = async (authorization: Authorization) => {
- const oldData = data;
- setApiQueryData<Authorization[]>(queryClient, [ENDPOINT], prevData =>
- prevData.filter(a => a.id !== authorization.id)
- );
- try {
- await api.requestPromise('/api-authorizations/', {
- method: 'DELETE',
- data: {authorization: authorization.id},
- });
- addSuccessMessage(t('Saved changes'));
- } catch (_err) {
- setApiQueryData<any>(queryClient, [ENDPOINT], oldData);
- addErrorMessage(t('Unable to save changes, please try again'));
- }
- };
- const isEmpty = data.length === 0;
- return (
- <SentryDocumentTitle title={t('Approved Applications')}>
- <SettingsPageHeader title="Authorized Applications" />
- <Description>
- {tct('You can manage your own applications via the [link:API dashboard].', {
- link: <Link to="/settings/account/api/" />,
- })}
- </Description>
- <Panel>
- <PanelHeader>{t('Approved Applications')}</PanelHeader>
- <PanelBody>
- {isEmpty && (
- <EmptyMessage>
- {t("You haven't approved any third party applications.")}
- </EmptyMessage>
- )}
- {!isEmpty && (
- <div>
- {data.map(authorization => (
- <PanelItemCenter key={authorization.id}>
- <ApplicationDetails>
- <ApplicationName>{authorization.application.name}</ApplicationName>
- {authorization.homepageUrl && (
- <Url>
- <ExternalLink href={authorization.homepageUrl}>
- {authorization.homepageUrl}
- </ExternalLink>
- </Url>
- )}
- <Scopes>{authorization.scopes.join(', ')}</Scopes>
- </ApplicationDetails>
- <Button
- size="sm"
- onClick={() => handleRevoke(authorization)}
- icon={<IconDelete />}
- aria-label={t('Delete')}
- data-test-id={authorization.id}
- />
- </PanelItemCenter>
- ))}
- </div>
- )}
- </PanelBody>
- </Panel>
- </SentryDocumentTitle>
- );
- }
- export default AccountAuthorizations;
- const Description = styled('p')`
- font-size: ${p => p.theme.fontSizeRelativeSmall};
- margin-bottom: ${space(4)};
- `;
- const PanelItemCenter = styled(PanelItem)`
- align-items: center;
- `;
- const ApplicationDetails = styled('div')`
- display: flex;
- flex: 1;
- flex-direction: column;
- `;
- const ApplicationName = styled('div')`
- font-weight: ${p => p.theme.fontWeightBold};
- margin-bottom: ${space(0.5)};
- `;
- /**
- * Intentionally wrap <a> so that it does not take up full width and cause
- * hit box issues
- */
- const Url = styled('div')`
- margin-bottom: ${space(0.5)};
- font-size: ${p => p.theme.fontSizeRelativeSmall};
- `;
- const Scopes = styled('div')`
- color: ${p => p.theme.gray300};
- font-size: ${p => p.theme.fontSizeRelativeSmall};
- `;
|