accountAuthorizations.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import {Link, RouteComponentProps} from 'react-router';
  2. import styled from '@emotion/styled';
  3. import {addErrorMessage, addSuccessMessage} from 'app/actionCreators/indicator';
  4. import Button from 'app/components/button';
  5. import {Panel, PanelBody, PanelHeader, PanelItem} from 'app/components/panels';
  6. import {IconDelete} from 'app/icons';
  7. import {t, tct} from 'app/locale';
  8. import space from 'app/styles/space';
  9. import {ApiApplication} from 'app/types';
  10. import AsyncView from 'app/views/asyncView';
  11. import EmptyMessage from 'app/views/settings/components/emptyMessage';
  12. import SettingsPageHeader from 'app/views/settings/components/settingsPageHeader';
  13. type Authorization = {
  14. application: ApiApplication;
  15. homepageUrl: string;
  16. id: string;
  17. scopes: string[];
  18. };
  19. type Props = RouteComponentProps<{}, {}>;
  20. type State = {
  21. data: Authorization[];
  22. } & AsyncView['state'];
  23. class AccountAuthorizations extends AsyncView<Props, State> {
  24. getEndpoints(): ReturnType<AsyncView['getEndpoints']> {
  25. return [['data', '/api-authorizations/']];
  26. }
  27. getTitle() {
  28. return 'Approved Applications';
  29. }
  30. handleRevoke = authorization => {
  31. const oldData = this.state.data;
  32. this.setState(
  33. state => ({
  34. data: state.data.filter(({id}) => id !== authorization.id),
  35. }),
  36. async () => {
  37. try {
  38. await this.api.requestPromise('/api-authorizations/', {
  39. method: 'DELETE',
  40. data: {authorization: authorization.id},
  41. });
  42. addSuccessMessage(t('Saved changes'));
  43. } catch (_err) {
  44. this.setState({
  45. data: oldData,
  46. });
  47. addErrorMessage(t('Unable to save changes, please try again'));
  48. }
  49. }
  50. );
  51. };
  52. renderBody() {
  53. const {data} = this.state;
  54. const isEmpty = data.length === 0;
  55. return (
  56. <div>
  57. <SettingsPageHeader title="Authorized Applications" />
  58. <Description>
  59. {tct('You can manage your own applications via the [link:API dashboard].', {
  60. link: <Link to="/settings/account/api/" />,
  61. })}
  62. </Description>
  63. <Panel>
  64. <PanelHeader>{t('Approved Applications')}</PanelHeader>
  65. <PanelBody>
  66. {isEmpty && (
  67. <EmptyMessage>
  68. {t("You haven't approved any third party applications.")}
  69. </EmptyMessage>
  70. )}
  71. {!isEmpty && (
  72. <div>
  73. {data.map(authorization => (
  74. <PanelItemCenter key={authorization.id}>
  75. <ApplicationDetails>
  76. <ApplicationName>{authorization.application.name}</ApplicationName>
  77. {authorization.homepageUrl && (
  78. <Url>
  79. <a href={authorization.homepageUrl}>
  80. {authorization.homepageUrl}
  81. </a>
  82. </Url>
  83. )}
  84. <Scopes>{authorization.scopes.join(', ')}</Scopes>
  85. </ApplicationDetails>
  86. <Button
  87. size="small"
  88. onClick={() => this.handleRevoke(authorization)}
  89. icon={<IconDelete />}
  90. />
  91. </PanelItemCenter>
  92. ))}
  93. </div>
  94. )}
  95. </PanelBody>
  96. </Panel>
  97. </div>
  98. );
  99. }
  100. }
  101. export default AccountAuthorizations;
  102. const Description = styled('p')`
  103. font-size: ${p => p.theme.fontSizeRelativeSmall};
  104. margin-bottom: ${space(4)};
  105. `;
  106. const PanelItemCenter = styled(PanelItem)`
  107. align-items: center;
  108. `;
  109. const ApplicationDetails = styled('div')`
  110. display: flex;
  111. flex: 1;
  112. flex-direction: column;
  113. `;
  114. const ApplicationName = styled('div')`
  115. font-weight: bold;
  116. margin-bottom: ${space(0.5)};
  117. `;
  118. /**
  119. * Intentionally wrap <a> so that it does not take up full width and cause
  120. * hit box issues
  121. */
  122. const Url = styled('div')`
  123. margin-bottom: ${space(0.5)};
  124. font-size: ${p => p.theme.fontSizeRelativeSmall};
  125. `;
  126. const Scopes = styled('div')`
  127. color: ${p => p.theme.gray300};
  128. font-size: ${p => p.theme.fontSizeRelativeSmall};
  129. `;