diff --git a/src/actions/challenges.js b/src/actions/challenges.js index 4c52f003..641b9542 100644 --- a/src/actions/challenges.js +++ b/src/actions/challenges.js @@ -28,9 +28,6 @@ import { UPLOAD_ATTACHMENT_FAILURE, UPLOAD_ATTACHMENT_PENDING, UPLOAD_ATTACHMENT_SUCCESS, - LOAD_CHALLENGE_RESOURCES_PENDING, - LOAD_CHALLENGE_RESOURCES_SUCCESS, - LOAD_CHALLENGE_RESOURCES_FAILURE, CREATE_CHALLENGE_RESOURCE, DELETE_CHALLENGE_RESOURCE, REMOVE_ATTACHMENT, @@ -40,7 +37,8 @@ import { UPDATE_CHALLENGE_DETAILS_FAILURE, CREATE_CHALLENGE_PENDING, CREATE_CHALLENGE_SUCCESS, - CREATE_CHALLENGE_FAILURE + CREATE_CHALLENGE_FAILURE, + LOAD_CHALLENGE_RESOURCES } from '../config/constants' import { loadProject } from './projects' @@ -396,27 +394,11 @@ export function loadChallengeTerms () { } export function loadResources (challengeId) { - return async (dispatch) => { - dispatch({ - type: LOAD_CHALLENGE_RESOURCES_PENDING, - challengeResources: {} - }) - + return (dispatch, getState) => { if (challengeId) { - return fetchResources(challengeId).then((resources) => { - dispatch({ - type: LOAD_CHALLENGE_RESOURCES_SUCCESS, - challengeResources: resources - }) - }).catch(() => { - dispatch({ - type: LOAD_CHALLENGE_RESOURCES_FAILURE - }) - }) - } else { - dispatch({ - type: LOAD_CHALLENGE_RESOURCES_SUCCESS, - challengeResources: null + return dispatch({ + type: LOAD_CHALLENGE_RESOURCES, + payload: fetchResources(challengeId) }) } } diff --git a/src/components/ChallengeEditor/ChallengeView/index.js b/src/components/ChallengeEditor/ChallengeView/index.js index 31b74caf..164787f6 100644 --- a/src/components/ChallengeEditor/ChallengeView/index.js +++ b/src/components/ChallengeEditor/ChallengeView/index.js @@ -21,7 +21,16 @@ import PhaseInput from '../../PhaseInput' import LegacyLinks from '../../LegacyLinks' import AssignedMemberField from '../AssignedMember-Field' -const ChallengeView = ({ projectDetail, challenge, metadata, challengeResources, token, isLoading, challengeId, assignedMemberDetails }) => { +const ChallengeView = ({ + projectDetail, + challenge, + metadata, + challengeResources, + token, + isLoading, + challengeId, + assignedMemberDetails, + enableEdit }) => { const selectedType = _.find(metadata.challengeTypes, { id: challenge.typeId }) const challengeTrack = _.find(metadata.challengeTracks, { id: challenge.trackId }) @@ -64,7 +73,7 @@ const ChallengeView = ({ projectDetail, challenge, metadata, challengeResources,
View Details
- + { enableEdit && }
@@ -202,7 +211,8 @@ ChallengeView.propTypes = { isLoading: PropTypes.bool.isRequired, challengeId: PropTypes.string.isRequired, challengeResources: PropTypes.arrayOf(PropTypes.object), - assignedMemberDetails: PropTypes.shape() + assignedMemberDetails: PropTypes.shape(), + enableEdit: PropTypes.bool } export default withRouter(ChallengeView) diff --git a/src/config/constants.js b/src/config/constants.js index 833c02e5..5ae0d333 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -66,6 +66,7 @@ export const UPLOAD_ATTACHMENT_PENDING = 'UPLOAD_ATTACHMENT_PENDING' export const UPLOAD_ATTACHMENT_FAILURE = 'UPLOAD_ATTACHMENT_FAILURE' export const UPLOAD_ATTACHMENT_SUCCESS = 'UPLOAD_ATTACHMENT_SUCCESS' +export const LOAD_CHALLENGE_RESOURCES = 'LOAD_CHALLENGE_RESOURCES' export const LOAD_CHALLENGE_RESOURCES_SUCCESS = 'LOAD_CHALLENGE_RESOURCES_SUCCESS' export const LOAD_CHALLENGE_RESOURCES_PENDING = 'LOAD_CHALLENGE_RESOURCES_PENDING' export const LOAD_CHALLENGE_RESOURCES_FAILURE = 'LOAD_CHALLENGE_RESOURCES_FAILURE' diff --git a/src/containers/ChallengeEditor/ChallengeEditor.module.scss b/src/containers/ChallengeEditor/ChallengeEditor.module.scss new file mode 100644 index 00000000..ce43f8f3 --- /dev/null +++ b/src/containers/ChallengeEditor/ChallengeEditor.module.scss @@ -0,0 +1,7 @@ +@import "../../styles/includes"; + + +.errorContainer { + color: $red; + padding: 10px; +} \ No newline at end of file diff --git a/src/containers/ChallengeEditor/index.js b/src/containers/ChallengeEditor/index.js index 7f6e35a0..75aae0c2 100644 --- a/src/containers/ChallengeEditor/index.js +++ b/src/containers/ChallengeEditor/index.js @@ -5,6 +5,7 @@ import { withRouter, Route } from 'react-router-dom' import ChallengeEditorComponent from '../../components/ChallengeEditor' import ChallengeViewComponent from '../../components/ChallengeEditor/ChallengeView' import Loader from '../../components/Loader' +import styles from './ChallengeEditor.module.scss' import { loadTimelineTemplates, @@ -113,10 +114,21 @@ class ChallengeEditor extends Component { loadChallengeDetails(projectId, challengeId) } + isEditable () { + const { hasProjectAccess, metadata: { resourceRoles }, challengeResources, loggedInUser } = this.props + if (!hasProjectAccess) { + return false + } + const userRoles = _.filter(challengeResources, cr => cr.memberId === `${loggedInUser.userId}`) + const userResourceRoles = _.filter(resourceRoles, rr => _.some(userRoles, ur => ur.roleId === rr.id)) + return _.some(userResourceRoles, urr => urr.fullAccess && urr.isActive) + } + render () { const { match, isLoading, + isProjectLoading, challengeDetails, challengeResources, metadata, @@ -132,6 +144,7 @@ class ChallengeEditor extends Component { replaceResourceInRole // members } = this.props + if (isProjectLoading || isLoading) return const challengeId = _.get(match.params, 'challengeId', null) if (challengeId && (!challengeDetails || !challengeDetails.id)) { return () @@ -144,6 +157,7 @@ class ChallengeEditor extends Component { handle: submitters[0].memberHandle } } + const enableEdit = this.isEditable() return
)) } /> - You don't have access to edit the challenge
} + { enableEdit && (( @@ -196,6 +211,7 @@ class ChallengeEditor extends Component { /> )) } /> + } )) } /> @@ -237,6 +254,8 @@ ChallengeEditor.propTypes = { loadResourceRoles: PropTypes.func, challengeResources: PropTypes.arrayOf(PropTypes.object), challengeDetails: PropTypes.object, + isProjectLoading: PropTypes.bool, + hasProjectAccess: PropTypes.bool, projectDetail: PropTypes.object, // history: PropTypes.object, metadata: PropTypes.shape({ @@ -246,6 +265,7 @@ ChallengeEditor.propTypes = { createAttachment: PropTypes.func, attachments: PropTypes.arrayOf(PropTypes.shape()), token: PropTypes.string, + loggedInUser: PropTypes.object, removeAttachment: PropTypes.func, failedToLoad: PropTypes.bool, loadMemberDetails: PropTypes.func, @@ -256,14 +276,17 @@ ChallengeEditor.propTypes = { // members: PropTypes.arrayOf(PropTypes.shape()) } -const mapStateToProps = ({ projects: { projectDetail }, challenges: { challengeDetails, challengeResources, metadata, isLoading, attachments, failedToLoad }, auth: { token }, members: { members } }) => ({ +const mapStateToProps = ({ projects, challenges: { challengeDetails, challengeResources, metadata, isLoading, attachments, failedToLoad }, auth: { token, user }, members: { members } }) => ({ challengeDetails, - projectDetail, + hasProjectAccess: projects.hasProjectAccess, + projectDetail: projects.projectDetail, challengeResources, metadata, isLoading, + isProjectLoading: projects.isLoading, attachments, token, + loggedInUser: user, failedToLoad // members }) diff --git a/src/reducers/challenges.js b/src/reducers/challenges.js index bb60551e..df398975 100644 --- a/src/reducers/challenges.js +++ b/src/reducers/challenges.js @@ -166,7 +166,7 @@ export default function (state = initialState, action) { case LOAD_CHALLENGE_RESOURCES_SUCCESS: return { ...state, - challengeResources: action.challengeResources, + challengeResources: action.payload, isLoading: false, failedToLoad: false } diff --git a/src/reducers/projects.js b/src/reducers/projects.js index 0d28f3a2..f80e9a93 100644 --- a/src/reducers/projects.js +++ b/src/reducers/projects.js @@ -1,6 +1,7 @@ /** * Reducer to process actions related to project */ +import _ from 'lodash' import { LOAD_PROJECT_DETAILS_FAILURE, LOAD_PROJECT_DETAILS_PENDING, @@ -16,12 +17,15 @@ export default function (state = initialState, action) { switch (action.type) { case LOAD_PROJECT_DETAILS_PENDING: return { ...state, isLoading: true } - case LOAD_PROJECT_DETAILS_FAILURE: - return { ...state, isLoading: false } + case LOAD_PROJECT_DETAILS_FAILURE: { + const status = _.get(action, 'payload.response.status', 500) + return { ...state, isLoading: false, hasProjectAccess: status !== 403 } + } case LOAD_PROJECT_DETAILS_SUCCESS: return { ...state, projectDetail: action.payload, + hasProjectAccess: true, isLoading: false } default: