repository.tsx 4.1 KB

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