apiTokenRow.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import styled from '@emotion/styled';
  2. import {Button} from 'sentry/components/button';
  3. import Confirm from 'sentry/components/confirm';
  4. import {DateTime} from 'sentry/components/dateTime';
  5. import PanelItem from 'sentry/components/panels/panelItem';
  6. import {IconSubtract} from 'sentry/icons';
  7. import {t} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import type {InternalAppApiToken} from 'sentry/types';
  10. import getDynamicText from 'sentry/utils/getDynamicText';
  11. import {tokenPreview} from 'sentry/views/settings/organizationAuthTokens';
  12. type Props = {
  13. onRemove: (token: InternalAppApiToken) => void;
  14. token: InternalAppApiToken;
  15. tokenPrefix?: string;
  16. };
  17. function ApiTokenRow({token, onRemove, tokenPrefix = ''}: Props) {
  18. return (
  19. <StyledPanelItem>
  20. <Controls>
  21. {token.name ? token.name : ''}
  22. <ButtonWrapper>
  23. <Confirm
  24. onConfirm={() => onRemove(token)}
  25. message={t(
  26. 'Are you sure you want to revoke %s token? It will not be usable anymore, and this cannot be undone.',
  27. tokenPreview(token.tokenLastCharacters, tokenPrefix)
  28. )}
  29. >
  30. <Button
  31. data-test-id="token-delete"
  32. icon={<IconSubtract isCircled size="xs" />}
  33. >
  34. {t('Remove')}
  35. </Button>
  36. </Confirm>
  37. </ButtonWrapper>
  38. </Controls>
  39. <Details>
  40. <TokenWrapper>
  41. <Heading>{t('Token')}</Heading>
  42. <TokenPreview aria-label={t('Token preview')}>
  43. {tokenPreview(
  44. getDynamicText({
  45. value: token.tokenLastCharacters,
  46. fixed: 'ABCD',
  47. }),
  48. tokenPrefix
  49. )}
  50. </TokenPreview>
  51. </TokenWrapper>
  52. <ScopesWrapper>
  53. <Heading>{t('Scopes')}</Heading>
  54. <ScopeList>{token.scopes.join(', ')}</ScopeList>
  55. </ScopesWrapper>
  56. <div>
  57. <Heading>{t('Created')}</Heading>
  58. <Time>
  59. <DateTime
  60. date={getDynamicText({
  61. value: token.dateCreated,
  62. fixed: new Date(1508208080000), // National Pasta Day
  63. })}
  64. />
  65. </Time>
  66. </div>
  67. </Details>
  68. </StyledPanelItem>
  69. );
  70. }
  71. const StyledPanelItem = styled(PanelItem)`
  72. flex-direction: column;
  73. padding: ${space(2)};
  74. `;
  75. const Controls = styled('div')`
  76. display: flex;
  77. align-items: center;
  78. margin-bottom: ${space(1)};
  79. `;
  80. const Details = styled('div')`
  81. display: flex;
  82. margin-top: ${space(1)};
  83. `;
  84. const TokenWrapper = styled('div')`
  85. flex: 1;
  86. margin-right: ${space(1)};
  87. `;
  88. const ScopesWrapper = styled('div')`
  89. flex: 2;
  90. margin-right: ${space(4)};
  91. `;
  92. const ScopeList = styled('div')`
  93. font-size: ${p => p.theme.fontSizeRelativeSmall};
  94. line-height: 1.4;
  95. `;
  96. const Time = styled('time')`
  97. font-size: ${p => p.theme.fontSizeRelativeSmall};
  98. line-height: 1.4;
  99. `;
  100. const Heading = styled('div')`
  101. font-size: ${p => p.theme.fontSizeMedium};
  102. text-transform: uppercase;
  103. color: ${p => p.theme.subText};
  104. margin-bottom: ${space(1)};
  105. `;
  106. const TokenPreview = styled('div')`
  107. color: ${p => p.theme.gray300};
  108. `;
  109. const ButtonWrapper = styled('div')`
  110. margin-left: auto;
  111. display: flex;
  112. flex-direction: column;
  113. align-items: flex-end;
  114. justify-content: flex-end;
  115. font-size: ${p => p.theme.fontSizeSmall};
  116. gap: ${space(1)};
  117. `;
  118. export default ApiTokenRow;