repository.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import {useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import {Button} from 'sentry/components/button';
  4. import PanelItem from 'sentry/components/panels/panelItem';
  5. import {IconChevron} from 'sentry/icons/iconChevron';
  6. import {IconRefresh} from 'sentry/icons/iconRefresh';
  7. import {t} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import type {CustomRepo} from 'sentry/types/debugFiles';
  10. import {CustomRepoType} from 'sentry/types/debugFiles';
  11. import CustomRepositoryActions from './actions';
  12. import Details from './details';
  13. import Status from './status';
  14. import {customRepoTypeLabel} from './utils';
  15. type Props = {
  16. hasAccess: boolean;
  17. hasFeature: boolean;
  18. onDelete: (repositoryId: string) => void;
  19. onEdit: (repositoryId: string) => void;
  20. onSyncNow: (repositoryId: string) => void;
  21. repository: CustomRepo;
  22. };
  23. function Repository({
  24. repository,
  25. onSyncNow,
  26. onDelete,
  27. onEdit,
  28. hasFeature,
  29. hasAccess,
  30. }: Props) {
  31. const [isDetailsExpanded, setIsDetailsExpanded] = useState(false);
  32. const {id, name, type} = repository;
  33. if (repository.type === CustomRepoType.APP_STORE_CONNECT) {
  34. const authenticated = repository.details?.credentials.status !== 'invalid';
  35. const detailsAvailable = repository.details !== undefined;
  36. return (
  37. <StyledPanelItem>
  38. <ToggleDetails
  39. size="sm"
  40. aria-label={t('Toggle details')}
  41. onClick={() =>
  42. detailsAvailable ? setIsDetailsExpanded(!isDetailsExpanded) : undefined
  43. }
  44. direction={isDetailsExpanded ? 'down' : 'up'}
  45. />
  46. <Name>{name}</Name>
  47. <TypeAndStatus>
  48. {customRepoTypeLabel[type]}
  49. <Status details={repository.details} onEditRepository={() => onEdit(id)} />
  50. </TypeAndStatus>
  51. <CustomRepositoryActions
  52. repositoryName={name}
  53. repositoryType={type}
  54. hasFeature={hasFeature}
  55. hasAccess={hasAccess}
  56. onDelete={() => onDelete(id)}
  57. onEdit={() => onEdit(id)}
  58. disabled={repository.details === undefined}
  59. syncNowButton={
  60. <Button
  61. size="sm"
  62. onClick={() => onSyncNow(id)}
  63. icon={<IconRefresh />}
  64. disabled={!detailsAvailable || !authenticated || !hasFeature || !hasAccess}
  65. title={
  66. !hasFeature
  67. ? undefined
  68. : !hasAccess
  69. ? t(
  70. 'You do not have permission to edit custom repositories configurations.'
  71. )
  72. : !authenticated
  73. ? t(
  74. 'Authentication is required before this repository can sync with App Store Connect.'
  75. )
  76. : undefined
  77. }
  78. >
  79. {t('Sync Now')}
  80. </Button>
  81. }
  82. />
  83. {isDetailsExpanded && <Details details={repository.details} />}
  84. </StyledPanelItem>
  85. );
  86. }
  87. return (
  88. <StyledPanelItem>
  89. <Name>{name}</Name>
  90. <TypeAndStatus>{customRepoTypeLabel[type]}</TypeAndStatus>
  91. <CustomRepositoryActions
  92. repositoryName={name}
  93. repositoryType={type}
  94. hasFeature={hasFeature}
  95. hasAccess={hasAccess}
  96. onDelete={() => onDelete(id)}
  97. onEdit={() => onEdit(id)}
  98. />
  99. </StyledPanelItem>
  100. );
  101. }
  102. export default Repository;
  103. const StyledPanelItem = styled(PanelItem)`
  104. display: grid;
  105. align-items: flex-start;
  106. gap: ${space(1)};
  107. grid-template-columns: max-content 1fr;
  108. @media (min-width: ${p => p.theme.breakpoints.small}) {
  109. grid-template-columns: max-content 1fr max-content;
  110. }
  111. `;
  112. const Name = styled('div')`
  113. grid-column: 2/2;
  114. @media (min-width: ${p => p.theme.breakpoints.small}) {
  115. grid-column: 2/3;
  116. grid-row: 1/2;
  117. }
  118. `;
  119. const TypeAndStatus = styled('div')`
  120. color: ${p => p.theme.gray300};
  121. font-size: ${p => p.theme.fontSizeMedium};
  122. display: flex;
  123. flex-wrap: wrap;
  124. align-items: center;
  125. grid-column: 2/2;
  126. gap: ${space(1.5)};
  127. @media (min-width: ${p => p.theme.breakpoints.small}) {
  128. grid-column: 2/3;
  129. grid-row: 2/2;
  130. gap: ${space(1)};
  131. }
  132. `;
  133. const ToggleDetails = styled(IconChevron)`
  134. cursor: pointer;
  135. `;