clone-project-modal-content.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. /* eslint-disable jsx-a11y/no-autofocus */
  2. import PropTypes from 'prop-types'
  3. import { useCallback, useMemo, useState } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import {
  6. Modal,
  7. Alert,
  8. Button,
  9. ControlLabel,
  10. FormControl,
  11. FormGroup,
  12. } from 'react-bootstrap'
  13. import { postJSON } from '../../../infrastructure/fetch-json'
  14. import { CloneProjectTag } from './clone-project-tag'
  15. export default function CloneProjectModalContent({
  16. handleHide,
  17. inFlight,
  18. setInFlight,
  19. handleAfterCloned,
  20. projectId,
  21. projectName,
  22. projectTags,
  23. }) {
  24. const { t } = useTranslation()
  25. const [error, setError] = useState()
  26. const [clonedProjectName, setClonedProjectName] = useState(
  27. `${projectName} (Copy)`
  28. )
  29. const [clonedProjectTags, setClonedProjectTags] = useState(projectTags)
  30. // valid if the cloned project has a name
  31. const valid = useMemo(
  32. () => clonedProjectName.trim().length > 0,
  33. [clonedProjectName]
  34. )
  35. // form submission: clone the project if the name is valid
  36. const handleSubmit = event => {
  37. event.preventDefault()
  38. if (!valid) {
  39. return
  40. }
  41. setError(false)
  42. setInFlight(true)
  43. // clone the project
  44. postJSON(`/project/${projectId}/clone`, {
  45. body: {
  46. projectName: clonedProjectName,
  47. tags: clonedProjectTags.map(tag => ({ id: tag._id })),
  48. },
  49. })
  50. .then(data => {
  51. // open the cloned project
  52. handleAfterCloned(data, clonedProjectTags)
  53. })
  54. .catch(({ response, data }) => {
  55. if (response?.status === 400) {
  56. setError(data.message)
  57. } else {
  58. setError(true)
  59. }
  60. })
  61. .finally(() => {
  62. setInFlight(false)
  63. })
  64. }
  65. const removeTag = useCallback(tag => {
  66. setClonedProjectTags(value => value.filter(item => item._id !== tag._id))
  67. }, [])
  68. return (
  69. <>
  70. <Modal.Header closeButton>
  71. <Modal.Title>{t('copy_project')}</Modal.Title>
  72. </Modal.Header>
  73. <Modal.Body>
  74. <form id="clone-project-form" onSubmit={handleSubmit}>
  75. <FormGroup>
  76. <ControlLabel htmlFor="clone-project-form-name">
  77. {t('new_name')}
  78. </ControlLabel>
  79. <FormControl
  80. id="clone-project-form-name"
  81. type="text"
  82. placeholder="New Project Name"
  83. required
  84. value={clonedProjectName}
  85. onChange={event => setClonedProjectName(event.target.value)}
  86. autoFocus
  87. />
  88. </FormGroup>
  89. {clonedProjectTags.length > 0 && (
  90. <FormGroup className="clone-project-tag">
  91. <ControlLabel htmlFor="clone-project-tags-list">
  92. {t('tags')}:{' '}
  93. </ControlLabel>
  94. <div role="listbox" id="clone-project-tags-list">
  95. {clonedProjectTags.map(tag => (
  96. <CloneProjectTag
  97. key={tag._id}
  98. tag={tag}
  99. removeTag={removeTag}
  100. />
  101. ))}
  102. </div>
  103. </FormGroup>
  104. )}
  105. </form>
  106. {error && (
  107. <Alert bsStyle="danger">
  108. {error.length ? error : t('generic_something_went_wrong')}
  109. </Alert>
  110. )}
  111. </Modal.Body>
  112. <Modal.Footer>
  113. <Button
  114. type="button"
  115. bsStyle={null}
  116. className="btn-secondary"
  117. disabled={inFlight}
  118. onClick={handleHide}
  119. >
  120. {t('cancel')}
  121. </Button>
  122. <Button
  123. form="clone-project-form"
  124. type="submit"
  125. bsStyle="primary"
  126. disabled={inFlight || !valid}
  127. >
  128. {inFlight ? <>{t('copying')}…</> : t('copy')}
  129. </Button>
  130. </Modal.Footer>
  131. </>
  132. )
  133. }
  134. CloneProjectModalContent.propTypes = {
  135. handleHide: PropTypes.func.isRequired,
  136. inFlight: PropTypes.bool,
  137. setInFlight: PropTypes.func.isRequired,
  138. handleAfterCloned: PropTypes.func.isRequired,
  139. projectId: PropTypes.string,
  140. projectName: PropTypes.string,
  141. projectTags: PropTypes.arrayOf(
  142. PropTypes.shape({
  143. _id: PropTypes.string.isRequired,
  144. name: PropTypes.string.isRequired,
  145. color: PropTypes.string,
  146. })
  147. ),
  148. }