diff --git a/src/actions/challenges.js b/src/actions/challenges.js index 0cc3373c..d9aafda0 100644 --- a/src/actions/challenges.js +++ b/src/actions/challenges.js @@ -16,6 +16,7 @@ import { fetchGroupDetail, updateChallenge, patchChallenge, + deleteChallenge as deleteChallengeAPI, createChallenge as createChallengeAPI, createResource as createResourceAPI, deleteResource as deleteResourceAPI @@ -39,6 +40,9 @@ import { CREATE_CHALLENGE_PENDING, CREATE_CHALLENGE_SUCCESS, CREATE_CHALLENGE_FAILURE, + DELETE_CHALLENGE_PENDING, + DELETE_CHALLENGE_SUCCESS, + DELETE_CHALLENGE_FAILURE, LOAD_CHALLENGE_RESOURCES } from '../config/constants' import { loadProject } from './projects' @@ -276,6 +280,26 @@ export function partiallyUpdateChallengeDetails (challengeId, partialChallengeDe } } +export function deleteChallenge (challengeId) { + return async (dispatch) => { + dispatch({ + type: DELETE_CHALLENGE_PENDING + }) + + return deleteChallengeAPI(challengeId).then((challenge) => { + return dispatch({ + type: DELETE_CHALLENGE_SUCCESS, + challengeDetails: challenge + }) + }).catch((error) => { + dispatch({ + type: DELETE_CHALLENGE_FAILURE + }) + throw error + }) + } +} + export function loadTimelineTemplates () { return async (dispatch) => { const timelineTemplates = await fetchTimelineTemplates() diff --git a/src/components/ChallengeEditor/ChallengeEditor.module.scss b/src/components/ChallengeEditor/ChallengeEditor.module.scss index 4d7f5b24..cac38dd5 100644 --- a/src/components/ChallengeEditor/ChallengeEditor.module.scss +++ b/src/components/ChallengeEditor/ChallengeEditor.module.scss @@ -241,7 +241,7 @@ .actionButtons { position: absolute; top: 30px; - a { + a,button { height: 40px; } } @@ -251,7 +251,13 @@ } .actionButtonsRight { + display: flex; + align-items: center; right: 20px; + + button { + margin-right: 20px; + } } .buttonContainer { diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index 82281cc0..605044ff 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -68,6 +68,7 @@ class ChallengeEditor extends Component { super(props) this.state = { isLaunch: false, + isDeleteLaunch: false, isConfirm: false, isClose: false, isOpenAdvanceSettings: false, @@ -122,6 +123,8 @@ class ChallengeEditor extends Component { this.getAvailableTimelineTemplates = this.getAvailableTimelineTemplates.bind(this) this.autoUpdateChallengeThrottled = _.throttle(this.validateAndAutoUpdateChallenge.bind(this), 3000) // 3s this.updateResource = this.updateResource.bind(this) + this.onDeleteChallenge = this.onDeleteChallenge.bind(this) + this.deleteModalLaunch = this.deleteModalLaunch.bind(this) } componentDidMount () { @@ -132,6 +135,27 @@ class ChallengeEditor extends Component { this.resetChallengeData(this.setState.bind(this)) } + deleteModalLaunch () { + if (!this.state.isDeleteLaunch) { + this.setState({ isDeleteLaunch: true }) + } + } + + async onDeleteChallenge () { + const { deleteChallenge, challengeDetails, history } = this.props + try { + this.setState({ isSaving: true }) + // Call action to delete the challenge + await deleteChallenge(challengeDetails.id) + this.setState({ isSaving: false }) + this.resetModal() + history.push(`/projects/${challengeDetails.projectId}/challenges`) + } catch (e) { + const error = _.get(e, 'response.data.message', 'Unable to Delete the challenge') + this.setState({ isSaving: false, error }) + } + } + /** * Validates challenge and if its valid calling an autosave method * @@ -207,7 +231,7 @@ class ChallengeEditor extends Component { } resetModal () { - this.setState({ isLoading: false, isConfirm: false, isLaunch: false, error: null, isCloseTask: false }) + this.setState({ isLoading: false, isConfirm: false, isLaunch: false, error: null, isCloseTask: false, isDeleteLaunch: false }) } /** @@ -1391,6 +1415,19 @@ class ChallengeEditor extends Component { /> } + { + this.state.isDeleteLaunch && !this.state.isConfirm && ( + + ) + } { showTimeline && (
{getTitle(isNew)}
+ {this.props.challengeDetails.status === 'New' && }
* Required
@@ -1488,6 +1526,7 @@ ChallengeEditor.propTypes = { createChallenge: PropTypes.func, replaceResourceInRole: PropTypes.func, partiallyUpdateChallengeDetails: PropTypes.func.isRequired, + deleteChallenge: PropTypes.func.isRequired, loggedInUser: PropTypes.shape().isRequired } diff --git a/src/components/ChallengesComponent/ChallengeCard/ChallengeCard.module.scss b/src/components/ChallengesComponent/ChallengeCard/ChallengeCard.module.scss index bac62146..89614ec3 100644 --- a/src/components/ChallengesComponent/ChallengeCard/ChallengeCard.module.scss +++ b/src/components/ChallengesComponent/ChallengeCard/ChallengeCard.module.scss @@ -257,6 +257,31 @@ } } +.deleteButton { + height: 22px; + width: 86px; + border-radius: 11.5px; + display: flex; + justify-content: center; + align-items: center; + background-color: $tc-red; + border-color: $tc-red; + cursor: pointer; + + span { + @include roboto; + + font-size: 14px; + font-weight: 400; + line-height: 17px; + color: $white; + text-transform: capitalize; + display: flex; + justify-content: center; + align-items: center; + } +} + .icon { vertical-align: bottom; } diff --git a/src/components/ChallengesComponent/ChallengeCard/index.js b/src/components/ChallengesComponent/ChallengeCard/index.js index 9da8123f..9d9ad8bc 100644 --- a/src/components/ChallengesComponent/ChallengeCard/index.js +++ b/src/components/ChallengesComponent/ChallengeCard/index.js @@ -96,14 +96,20 @@ const getPhaseInfo = (c) => { * @param onUpdateLaunch * @returns {*} */ -const hoverComponents = (challenge, onUpdateLaunch) => { +const hoverComponents = (challenge, onUpdateLaunch, deleteModalLaunch) => { const communityAppUrl = `${COMMUNITY_APP_URL}/challenges/${challenge.id}` const directUrl = `${DIRECT_PROJECT_URL}/contest/detail?projectId=${challenge.legacyId}` const orUrl = `${ONLINE_REVIEW_URL}/review/actions/ViewProjectDetails?pid=${challenge.legacyId}` // NEW projects never have Legacy challenge created, so don't show links and "Activate" button for them at all if (challenge.status.toUpperCase() === CHALLENGE_STATUS.NEW) { - return null + if (challenge.status.toUpperCase() === CHALLENGE_STATUS.NEW) { + return ( + + ) + } } return challenge.legacyId ? ( @@ -177,10 +183,13 @@ class ChallengeCard extends React.Component { this.state = { isConfirm: false, isLaunch: false, + isDeleteLaunch: false, isSaving: false } this.onUpdateConfirm = this.onUpdateConfirm.bind(this) this.onUpdateLaunch = this.onUpdateLaunch.bind(this) + this.onDeleteChallenge = this.onDeleteChallenge.bind(this) + this.deleteModalLaunch = this.deleteModalLaunch.bind(this) this.resetModal = this.resetModal.bind(this) this.onLaunchChallenge = this.onLaunchChallenge.bind(this) } @@ -195,8 +204,14 @@ class ChallengeCard extends React.Component { } } + deleteModalLaunch () { + if (!this.state.isDeleteLaunch) { + this.setState({ isDeleteLaunch: true }) + } + } + resetModal () { - this.setState({ isConfirm: false, isLaunch: false }) + this.setState({ isConfirm: false, isLaunch: false, isDeleteLaunch: false }) } async onLaunchChallenge () { @@ -216,12 +231,39 @@ class ChallengeCard extends React.Component { } } + async onDeleteChallenge () { + const { deleteChallenge, challenge } = this.props + try { + this.setState({ isSaving: true }) + // Call action to delete the challenge + await deleteChallenge(challenge.id) + this.setState({ isSaving: false }) + this.resetModal() + } catch (e) { + const error = _.get(e, 'response.data.message', 'Unable to Delete the challenge') + this.setState({ isSaving: false, error }) + } + } + render () { - const { isLaunch, isConfirm, isSaving } = this.state + const { isLaunch, isConfirm, isSaving, isDeleteLaunch } = this.state const { challenge, shouldShowCurrentPhase, reloadChallengeList } = this.props const { phaseMessage, endTime } = getPhaseInfo(challenge) return (
+ { + isDeleteLaunch && !isConfirm && ( + + ) + } { isLaunch && !isConfirm && ( {endTime} )}
- {hoverComponents(challenge, this.onUpdateLaunch, this.props.showError)} + {hoverComponents(challenge, this.onUpdateLaunch, this.deleteModalLaunch)}
@@ -282,16 +324,15 @@ class ChallengeCard extends React.Component { ChallengeCard.defaultPrps = { shouldShowCurrentPhase: true, - showError: () => {}, reloadChallengeList: () => {} } ChallengeCard.propTypes = { challenge: PropTypes.object, shouldShowCurrentPhase: PropTypes.bool, - showError: PropTypes.func, reloadChallengeList: PropTypes.func, - partiallyUpdateChallengeDetails: PropTypes.func.isRequired + partiallyUpdateChallengeDetails: PropTypes.func.isRequired, + deleteChallenge: PropTypes.func.isRequired } export default withRouter(ChallengeCard) diff --git a/src/components/ChallengesComponent/ChallengeList/index.js b/src/components/ChallengesComponent/ChallengeList/index.js index fe33891d..941c27eb 100644 --- a/src/components/ChallengesComponent/ChallengeList/index.js +++ b/src/components/ChallengesComponent/ChallengeList/index.js @@ -102,7 +102,8 @@ class ChallengeList extends Component { page, perPage, totalChallenges, - partiallyUpdateChallengeDetails + partiallyUpdateChallengeDetails, + deleteChallenge } = this.props if (warnMessage) { return @@ -211,9 +212,9 @@ class ChallengeList extends Component { ) @@ -256,7 +257,8 @@ ChallengeList.propTypes = { page: PropTypes.number.isRequired, perPage: PropTypes.number.isRequired, totalChallenges: PropTypes.number.isRequired, - partiallyUpdateChallengeDetails: PropTypes.func.isRequired + partiallyUpdateChallengeDetails: PropTypes.func.isRequired, + deleteChallenge: PropTypes.func.isRequired } export default ChallengeList diff --git a/src/components/ChallengesComponent/index.js b/src/components/ChallengesComponent/index.js index c173492f..eb386d49 100644 --- a/src/components/ChallengesComponent/index.js +++ b/src/components/ChallengesComponent/index.js @@ -26,7 +26,8 @@ const ChallengesComponent = ({ page, perPage, totalChallenges, - partiallyUpdateChallengeDetails + partiallyUpdateChallengeDetails, + deleteChallenge }) => { return ( @@ -86,6 +87,7 @@ const ChallengesComponent = ({ perPage={perPage} totalChallenges={totalChallenges} partiallyUpdateChallengeDetails={partiallyUpdateChallengeDetails} + deleteChallenge={deleteChallenge} /> )}
@@ -109,7 +111,8 @@ ChallengesComponent.propTypes = { page: PropTypes.number.isRequired, perPage: PropTypes.number.isRequired, totalChallenges: PropTypes.number.isRequired, - partiallyUpdateChallengeDetails: PropTypes.func.isRequired + partiallyUpdateChallengeDetails: PropTypes.func.isRequired, + deleteChallenge: PropTypes.func.isRequired } ChallengesComponent.defaultProps = { diff --git a/src/config/constants.js b/src/config/constants.js index d1e4aef0..2b26b0f9 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -48,6 +48,10 @@ export const CREATE_CHALLENGE_SUCCESS = 'CREATE_CHALLENGE_SUCCESS' export const CREATE_CHALLENGE_PENDING = 'CREATE_CHALLENGE_PENDING' export const CREATE_CHALLENGE_FAILURE = 'CREATE_CHALLENGE_FAILURE' +export const DELETE_CHALLENGE_SUCCESS = 'DELETE_CHALLENGE_SUCCESS' +export const DELETE_CHALLENGE_PENDING = 'DELETE_CHALLENGE_PENDING' +export const DELETE_CHALLENGE_FAILURE = 'DELETE_CHALLENGE_FAILURE' + export const LOAD_PROJECT_DETAILS = 'LOAD_PROJECT_DETAILS' export const LOAD_PROJECT_DETAILS_SUCCESS = 'LOAD_PROJECT_DETAILS_SUCCESS' export const LOAD_PROJECT_DETAILS_PENDING = 'LOAD_PROJECT_DETAILS_PENDING' diff --git a/src/containers/ChallengeEditor/index.js b/src/containers/ChallengeEditor/index.js index a7bba88a..bc3cad37 100644 --- a/src/containers/ChallengeEditor/index.js +++ b/src/containers/ChallengeEditor/index.js @@ -24,6 +24,7 @@ import { loadResourceRoles, updateChallengeDetails, partiallyUpdateChallengeDetails, + deleteChallenge, createChallenge, replaceResourceInRole } from '../../actions/challenges' @@ -230,6 +231,7 @@ class ChallengeEditor extends Component { partiallyUpdateChallengeDetails, createChallenge, replaceResourceInRole, + deleteChallenge, loggedInUser // members } = this.props @@ -336,6 +338,7 @@ class ChallengeEditor extends Component { updateChallengeDetails={updateChallengeDetails} replaceResourceInRole={replaceResourceInRole} partiallyUpdateChallengeDetails={partiallyUpdateChallengeDetails} + deleteChallenge={deleteChallenge} loggedInUser={loggedInUser} /> )) @@ -402,6 +405,7 @@ ChallengeEditor.propTypes = { updateChallengeDetails: PropTypes.func.isRequired, partiallyUpdateChallengeDetails: PropTypes.func.isRequired, createChallenge: PropTypes.func.isRequired, + deleteChallenge: PropTypes.func.isRequired, replaceResourceInRole: PropTypes.func // members: PropTypes.arrayOf(PropTypes.shape()) } @@ -437,6 +441,7 @@ const mapDispatchToProps = { loadResourceRoles, updateChallengeDetails, partiallyUpdateChallengeDetails, + deleteChallenge, createChallenge, replaceResourceInRole } diff --git a/src/containers/Challenges/index.js b/src/containers/Challenges/index.js index e0b9ef5d..7632bb3f 100644 --- a/src/containers/Challenges/index.js +++ b/src/containers/Challenges/index.js @@ -10,7 +10,7 @@ import { DebounceInput } from 'react-debounce-input' import ChallengesComponent from '../../components/ChallengesComponent' import ProjectCard from '../../components/ProjectCard' import Loader from '../../components/Loader' -import { loadChallengesByPage, partiallyUpdateChallengeDetails } from '../../actions/challenges' +import { loadChallengesByPage, partiallyUpdateChallengeDetails, deleteChallenge } from '../../actions/challenges' import { loadProject } from '../../actions/projects' import { loadProjects, setActiveProject, resetSidebarActiveParams } from '../../actions/sidebar' import { @@ -86,7 +86,8 @@ class Challenges extends Component { perPage, totalChallenges, setActiveProject, - partiallyUpdateChallengeDetails + partiallyUpdateChallengeDetails, + deleteChallenge } = this.props const { searchProjectName, onlyMyProjects } = this.state const projectInfo = _.find(projects, { id: activeProjectId }) || {} @@ -147,6 +148,7 @@ class Challenges extends Component { perPage={perPage} totalChallenges={totalChallenges} partiallyUpdateChallengeDetails={partiallyUpdateChallengeDetails} + deleteChallenge={deleteChallenge} /> } @@ -173,7 +175,8 @@ Challenges.propTypes = { totalChallenges: PropTypes.number.isRequired, loadProjects: PropTypes.func.isRequired, setActiveProject: PropTypes.func.isRequired, - partiallyUpdateChallengeDetails: PropTypes.func.isRequired + partiallyUpdateChallengeDetails: PropTypes.func.isRequired, + deleteChallenge: PropTypes.func.isRequired } const mapStateToProps = ({ challenges, sidebar, projects }) => ({ @@ -191,7 +194,8 @@ const mapDispatchToProps = { loadProject, loadProjects, setActiveProject, - partiallyUpdateChallengeDetails + partiallyUpdateChallengeDetails, + deleteChallenge } export default connect(mapStateToProps, mapDispatchToProps)(Challenges) diff --git a/src/reducers/challenges.js b/src/reducers/challenges.js index df398975..20698d89 100644 --- a/src/reducers/challenges.js +++ b/src/reducers/challenges.js @@ -27,7 +27,10 @@ import { CREATE_CHALLENGE_RESOURCE_SUCCESS, DELETE_CHALLENGE_RESOURCE_SUCCESS, DELETE_CHALLENGE_RESOURCE_FAILURE, - CREATE_CHALLENGE_RESOURCE_FAILURE + CREATE_CHALLENGE_RESOURCE_FAILURE, + DELETE_CHALLENGE_SUCCESS, + DELETE_CHALLENGE_FAILURE, + DELETE_CHALLENGE_PENDING } from '../config/constants' const initialState = { @@ -42,6 +45,7 @@ const initialState = { attachments: [], challenge: null, filterChallengeName: '', + failedToDelete: false, status: '', perPage: 0, page: 1, @@ -144,6 +148,27 @@ export default function (state = initialState, action) { } case UPDATE_CHALLENGE_DETAILS_FAILURE: return { ...state, isLoading: false, attachments: [], challenge: null, failedToLoad: false, failedToUpdate: true } + + case DELETE_CHALLENGE_PENDING: + return { ...state, failedToLoad: false } + + case DELETE_CHALLENGE_SUCCESS: { + const deletedChallengeDetails = action.challengeDetails.data + const updatedChallenges = state.challenges.filter((challenge) => challenge.id !== deletedChallengeDetails.id) + toastrSuccess('Success', `Challenge deleted successfully.`) + return { + ...state, + challenges: updatedChallenges + } + } + + case DELETE_CHALLENGE_FAILURE: { + return { + ...state, + failedToDelete: true + } + } + case CREATE_CHALLENGE_SUCCESS: { // if we are showing the list of challenges with the same status as we just created, // then add the new challenge to the beginning of the current challenge list diff --git a/src/services/challenges.js b/src/services/challenges.js index 4d2372a3..62845652 100644 --- a/src/services/challenges.js +++ b/src/services/challenges.js @@ -171,6 +171,14 @@ export function patchChallenge (challengeId, params) { }) } +/* +* Deletes the challenge with the provided id. +* @param challengeId +*/ +export function deleteChallenge (challengeId) { + return axiosInstance.delete(`${CHALLENGE_API_URL}/${challengeId}`) +} + /** * Api request for fetching challenge terms * @returns {Promise<*>}