Skip to content

Commit f924f93

Browse files
committed
fix: issue #1046
1 parent 6754b03 commit f924f93

File tree

8 files changed

+135
-9
lines changed

8 files changed

+135
-9
lines changed

src/actions/auth.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import { configureConnector, decodeToken } from 'tc-auth-lib'
2+
import _ from 'lodash'
23
import { fetchProfile } from '../services/user'
34
import {
5+
fetchResources,
6+
fetchResourceRoles
7+
} from '../services/challenges'
8+
import {
9+
CHECK_CHALLENGE_PERMISSON,
10+
CHECK_CHALLENGE_PERMISSON_SUCCESS,
411
LOAD_USER_SUCCESS,
512
SAVE_AUTH_TOKEN
613
} from '../config/constants'
14+
import { checkAdmin } from '../util/tc'
715

816
const { ACCOUNTS_APP_CONNECTOR_URL } = process.env
917

@@ -52,3 +60,50 @@ export function saveToken (token) {
5260
dispatch(loadUser(handle))
5361
}
5462
}
63+
64+
/**
65+
* check edit permission
66+
*/
67+
export function checkChallengeEditPermission (challengeId) {
68+
return (dispatch, getState) => {
69+
const state = getState()
70+
const token = state.auth.token
71+
const loggedInUser = state.auth.user
72+
const hasProjectAccess = state.projects.hasProjectAccess
73+
74+
const isAdmin = checkAdmin(token)
75+
if (isAdmin) {
76+
return dispatch({
77+
type: CHECK_CHALLENGE_PERMISSON_SUCCESS,
78+
hasPermission: true
79+
})
80+
}
81+
if (!hasProjectAccess) {
82+
return dispatch({
83+
type: CHECK_CHALLENGE_PERMISSON_SUCCESS,
84+
hasPermission: false
85+
})
86+
}
87+
88+
return dispatch({
89+
type: CHECK_CHALLENGE_PERMISSON,
90+
payload: Promise.all([
91+
fetchResources(challengeId),
92+
fetchResourceRoles()
93+
]).then(([challengeResources, resourceRoles]) => {
94+
const userRoles = _.filter(
95+
challengeResources,
96+
cr => cr.memberId === `${loggedInUser.userId}`
97+
)
98+
const userResourceRoles = _.filter(resourceRoles, rr =>
99+
_.some(userRoles, ur => ur.roleId === rr.id)
100+
)
101+
const hasPermission = _.some(
102+
userResourceRoles,
103+
urr => urr.fullWriteAccess && urr.isActive
104+
)
105+
return { hasPermission }
106+
})
107+
})
108+
}
109+
}

src/components/ChallengesComponent/ChallengeCard/index.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const DRAFT_MSG = 'In Draft'
2828
const STALLED_TIME_LEFT_MSG = 'Challenge is currently on hold'
2929
const FF_TIME_LEFT_MSG = 'Winner is working on fixes'
3030

31+
const PERMISSION_DELETE_MESSAGE_ERROR = "You don't have permission to delete this challenge"
32+
3133
/**
3234
* Format the remaining time of a challenge phase
3335
* @param phase Challenge phase
@@ -208,7 +210,12 @@ class ChallengeCard extends React.Component {
208210
}
209211

210212
deleteModalLaunch () {
213+
const {
214+
challenge,
215+
checkEditPermission
216+
} = this.props
211217
if (!this.state.isDeleteLaunch) {
218+
checkEditPermission(challenge.id)
212219
this.setState({ isDeleteLaunch: true })
213220
}
214221
}
@@ -255,18 +262,24 @@ class ChallengeCard extends React.Component {
255262

256263
render () {
257264
const { isLaunch, isConfirm, isSaving, isDeleteLaunch } = this.state
258-
const { challenge, shouldShowCurrentPhase, reloadChallengeList } = this.props
265+
const { challenge, shouldShowCurrentPhase, reloadChallengeList,
266+
isCheckChalengePermission,
267+
hasEditChallengePermission
268+
} = this.props
259269
const { phaseMessage, endTime } = getPhaseInfo(challenge)
270+
const deleteMessage = isCheckChalengePermission ? 'Checking permissions...' : `Do you want to delete "${challenge.name}"?`
271+
260272
return (
261273
<div className={styles.item}>
262274
{
263275
isDeleteLaunch && !isConfirm && (
264276
<ConfirmationModal
265277
title='Confirm Delete'
266-
message={`Do you want to delete "${challenge.name}"?`}
278+
message={deleteMessage}
267279
theme={theme}
268280
isProcessing={isSaving}
269-
errorMessage={this.state.error}
281+
disableConfirmButton={!hasEditChallengePermission}
282+
errorMessage={!isCheckChalengePermission && !hasEditChallengePermission ? PERMISSION_DELETE_MESSAGE_ERROR : this.state.error}
270283
onCancel={this.resetModal}
271284
onConfirm={this.onDeleteChallenge}
272285
/>
@@ -338,8 +351,11 @@ ChallengeCard.defaultPrps = {
338351
ChallengeCard.propTypes = {
339352
challenge: PropTypes.object,
340353
shouldShowCurrentPhase: PropTypes.bool,
354+
isCheckChalengePermission: PropTypes.bool,
355+
hasEditChallengePermission: PropTypes.bool,
341356
reloadChallengeList: PropTypes.func,
342357
partiallyUpdateChallengeDetails: PropTypes.func.isRequired,
358+
checkEditPermission: PropTypes.func.isRequired,
343359
deleteChallenge: PropTypes.func.isRequired
344360
}
345361

src/components/ChallengesComponent/ChallengeList/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,16 @@ class ChallengeList extends Component {
9696
const { searchText, errorMessage } = this.state
9797
const {
9898
activeProject,
99+
isCheckChalengePermission,
100+
hasEditChallengePermission,
99101
warnMessage,
100102
challenges,
101103
status,
102104
page,
103105
perPage,
104106
totalChallenges,
105107
partiallyUpdateChallengeDetails,
108+
checkEditPermission,
106109
deleteChallenge
107110
} = this.props
108111
if (warnMessage) {
@@ -212,8 +215,11 @@ class ChallengeList extends Component {
212215
<ChallengeCard
213216
shouldShowCurrentPhase={selectedTab === 0}
214217
challenge={c}
218+
isCheckChalengePermission={isCheckChalengePermission}
219+
hasEditChallengePermission={hasEditChallengePermission}
215220
reloadChallengeList={this.reloadChallengeList}
216221
partiallyUpdateChallengeDetails={partiallyUpdateChallengeDetails}
222+
checkEditPermission={checkEditPermission}
217223
deleteChallenge={deleteChallenge}
218224
/>
219225
</li>
@@ -251,14 +257,17 @@ ChallengeList.propTypes = {
251257
}),
252258
warnMessage: PropTypes.string,
253259
filterChallengeName: PropTypes.string,
260+
isCheckChalengePermission: PropTypes.bool,
261+
hasEditChallengePermission: PropTypes.bool,
254262
status: PropTypes.string,
255263
activeProjectId: PropTypes.number,
256264
loadChallengesByPage: PropTypes.func.isRequired,
257265
page: PropTypes.number.isRequired,
258266
perPage: PropTypes.number.isRequired,
259267
totalChallenges: PropTypes.number.isRequired,
260268
partiallyUpdateChallengeDetails: PropTypes.func.isRequired,
261-
deleteChallenge: PropTypes.func.isRequired
269+
deleteChallenge: PropTypes.func.isRequired,
270+
checkEditPermission: PropTypes.func.isRequired
262271
}
263272

264273
export default ChallengeList

src/components/ChallengesComponent/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import xss from 'xss'
1717
const ChallengesComponent = ({
1818
challenges,
1919
isLoading,
20+
isCheckChalengePermission,
21+
hasEditChallengePermission,
2022
warnMessage,
2123
filterChallengeName,
2224
activeProject,
@@ -27,6 +29,7 @@ const ChallengesComponent = ({
2729
perPage,
2830
totalChallenges,
2931
partiallyUpdateChallengeDetails,
32+
checkEditPermission,
3033
deleteChallenge
3134
}) => {
3235
return (
@@ -77,6 +80,8 @@ const ChallengesComponent = ({
7780
) : (
7881
<ChallengeList
7982
challenges={challenges}
83+
isCheckChalengePermission={isCheckChalengePermission}
84+
hasEditChallengePermission={hasEditChallengePermission}
8085
warnMessage={warnMessage}
8186
activeProject={activeProject}
8287
filterChallengeName={filterChallengeName}
@@ -87,6 +92,7 @@ const ChallengesComponent = ({
8792
perPage={perPage}
8893
totalChallenges={totalChallenges}
8994
partiallyUpdateChallengeDetails={partiallyUpdateChallengeDetails}
95+
checkEditPermission={checkEditPermission}
9096
deleteChallenge={deleteChallenge}
9197
/>
9298
)}
@@ -103,6 +109,8 @@ ChallengesComponent.propTypes = {
103109
name: PropTypes.string
104110
}),
105111
isLoading: PropTypes.bool,
112+
isCheckChalengePermission: PropTypes.bool,
113+
hasEditChallengePermission: PropTypes.bool,
106114
warnMessage: PropTypes.string,
107115
filterChallengeName: PropTypes.string,
108116
status: PropTypes.string,
@@ -112,7 +120,8 @@ ChallengesComponent.propTypes = {
112120
perPage: PropTypes.number.isRequired,
113121
totalChallenges: PropTypes.number.isRequired,
114122
partiallyUpdateChallengeDetails: PropTypes.func.isRequired,
115-
deleteChallenge: PropTypes.func.isRequired
123+
deleteChallenge: PropTypes.func.isRequired,
124+
checkEditPermission: PropTypes.func.isRequired
116125
}
117126

118127
ChallengesComponent.defaultProps = {

src/components/Modal/ConfirmationModal.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import styles from './ConfirmationModal.module.scss'
66
import OutlineButton from '../Buttons/OutlineButton'
77
import PrimaryButton from '../Buttons/PrimaryButton'
88

9-
const ConfirmationModal = ({ title, message, errorMessage, theme, isProcessing, onCancel, onConfirm }) => (
9+
const ConfirmationModal = ({ title, message, errorMessage, theme, isProcessing, onCancel, onConfirm, disableConfirmButton }) => (
1010
<Modal theme={theme} onCancel={onCancel}>
1111
<div className={styles.contentContainer}>
1212
<div className={styles.title}>{title}</div>
@@ -23,6 +23,7 @@ const ConfirmationModal = ({ title, message, errorMessage, theme, isProcessing,
2323
<div className={styles.button}>
2424
<PrimaryButton
2525
text={isProcessing ? 'Processing...' : 'Confirm'}
26+
disabled={disableConfirmButton}
2627
type={'info'}
2728
onClick={onConfirm}
2829
/>
@@ -39,6 +40,7 @@ ConfirmationModal.propTypes = {
3940
errorMessage: PropTypes.string,
4041
theme: PropTypes.shape(),
4142
isProcessing: PropTypes.bool,
43+
disableConfirmButton: PropTypes.bool,
4244
onCancel: PropTypes.func,
4345
onConfirm: PropTypes.func
4446
}

src/config/constants.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ export const LOAD_PROJECTS_SUCCESS = 'LOAD_PROJECTS_SUCCESS'
4343
export const LOAD_PROJECTS_PENDING = 'LOAD_PROJECTS_PENDING'
4444
export const LOAD_PROJECTS_FAILURE = 'LOAD_PROJECTS_FAILURE'
4545

46+
// check challenge permission
47+
export const CHECK_CHALLENGE_PERMISSON = 'CHECK_CHALLENGE_PERMISSON'
48+
export const CHECK_CHALLENGE_PERMISSON_SUCCESS = 'CHECK_CHALLENGE_PERMISSON_SUCCESS'
49+
export const CHECK_CHALLENGE_PERMISSON_PENDING = 'CHECK_CHALLENGE_PERMISSON_PENDING'
50+
export const CHECK_CHALLENGE_PERMISSON_FAILURE = 'CHECK_CHALLENGE_PERMISSON_FAILURE'
51+
4652
export const SET_ACTIVE_PROJECT = 'SET_ACTIVE_PROJECT'
4753

4854
export const LOAD_USER_SUCCESS = 'LOAD_USER_SUCCESS'

src/containers/Challenges/index.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import ChallengesComponent from '../../components/ChallengesComponent'
1111
import ProjectCard from '../../components/ProjectCard'
1212
import Loader from '../../components/Loader'
1313
import { loadChallengesByPage, partiallyUpdateChallengeDetails, deleteChallenge } from '../../actions/challenges'
14+
import { checkChallengeEditPermission } from '../../actions/auth'
1415
import { loadProject } from '../../actions/projects'
1516
import { loadProjects, setActiveProject, resetSidebarActiveParams } from '../../actions/sidebar'
1617
import {
@@ -27,6 +28,7 @@ class Challenges extends Component {
2728
}
2829

2930
this.updateProjectName = this.updateProjectName.bind(this)
31+
this.checkEditPermission = this.checkEditPermission.bind(this)
3032
this.toggleMyProjects = this.toggleMyProjects.bind(this)
3133
}
3234

@@ -48,6 +50,10 @@ class Challenges extends Component {
4850
}
4951
}
5052

53+
checkEditPermission (challengeId) {
54+
this.props.checkChallengeEditPermission(challengeId)
55+
}
56+
5157
reloadChallenges (props) {
5258
const { activeProjectId, projectDetail: reduxProjectInfo, projectId, challengeProjectId, loadProject } = props
5359
if (activeProjectId !== challengeProjectId) {
@@ -75,6 +81,8 @@ class Challenges extends Component {
7581
const {
7682
challenges,
7783
isLoading,
84+
isCheckChalengePermission,
85+
hasEditChallengePermission,
7886
warnMessage,
7987
filterChallengeName,
8088
projects,
@@ -144,11 +152,14 @@ class Challenges extends Component {
144152
status={status}
145153
activeProjectId={activeProjectId}
146154
loadChallengesByPage={loadChallengesByPage}
155+
isCheckChalengePermission={isCheckChalengePermission}
156+
hasEditChallengePermission={hasEditChallengePermission}
147157
page={page}
148158
perPage={perPage}
149159
totalChallenges={totalChallenges}
150160
partiallyUpdateChallengeDetails={partiallyUpdateChallengeDetails}
151161
deleteChallenge={deleteChallenge}
162+
checkEditPermission={this.checkEditPermission}
152163
/>
153164
}
154165
</Fragment>
@@ -162,6 +173,8 @@ Challenges.propTypes = {
162173
challenges: PropTypes.arrayOf(PropTypes.object),
163174
projectDetail: PropTypes.object,
164175
isLoading: PropTypes.bool,
176+
isCheckChalengePermission: PropTypes.bool,
177+
hasEditChallengePermission: PropTypes.bool,
165178
loadChallengesByPage: PropTypes.func,
166179
loadProject: PropTypes.func.isRequired,
167180
projectId: PropTypes.string,
@@ -176,11 +189,14 @@ Challenges.propTypes = {
176189
loadProjects: PropTypes.func.isRequired,
177190
setActiveProject: PropTypes.func.isRequired,
178191
partiallyUpdateChallengeDetails: PropTypes.func.isRequired,
192+
checkChallengeEditPermission: PropTypes.func.isRequired,
179193
deleteChallenge: PropTypes.func.isRequired
180194
}
181195

182-
const mapStateToProps = ({ challenges, sidebar, projects }) => ({
196+
const mapStateToProps = ({ challenges, sidebar, projects, auth }) => ({
183197
..._.omit(challenges, ['projectId']),
198+
isCheckChalengePermission: auth.isCheckChalengePermission,
199+
hasEditChallengePermission: auth.hasEditChallengePermission,
184200
challengeProjectId: challenges.projectId,
185201
activeProjectId: sidebar.activeProjectId,
186202
projects: sidebar.projects,
@@ -192,6 +208,7 @@ const mapDispatchToProps = {
192208
loadChallengesByPage,
193209
resetSidebarActiveParams,
194210
loadProject,
211+
checkChallengeEditPermission,
195212
loadProjects,
196213
setActiveProject,
197214
partiallyUpdateChallengeDetails,

src/reducers/auth.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
/**
22
* Reducer to process authentication actions
33
*/
4-
import { LOAD_USER_SUCCESS, SAVE_AUTH_TOKEN } from '../config/constants'
4+
import { LOAD_USER_SUCCESS, SAVE_AUTH_TOKEN,
5+
CHECK_CHALLENGE_PERMISSON_PENDING,
6+
CHECK_CHALLENGE_PERMISSON_SUCCESS,
7+
CHECK_CHALLENGE_PERMISSON_FAILURE
8+
} from '../config/constants'
59

610
const initialState = {
711
isLoading: true,
812
isLoggedIn: false,
913
user: null,
10-
token: null
14+
token: null,
15+
isCheckChalengePermission: false,
16+
hasEditChallengePermission: false
1117
}
1218

1319
export default function (state = initialState, action) {
@@ -24,6 +30,12 @@ export default function (state = initialState, action) {
2430
return { ...state, user: action.user, isLoading: false, isLoggedIn: true }
2531
case SAVE_AUTH_TOKEN:
2632
return { ...state, token: action.token }
33+
case CHECK_CHALLENGE_PERMISSON_PENDING:
34+
return { ...state, hasEditChallengePermission: false, isCheckChalengePermission: true }
35+
case CHECK_CHALLENGE_PERMISSON_SUCCESS:
36+
return { ...state, isCheckChalengePermission: false, hasEditChallengePermission: action.hasPermission }
37+
case CHECK_CHALLENGE_PERMISSON_FAILURE:
38+
return { ...state, isCheckChalengePermission: false }
2739
default:
2840
return state
2941
}

0 commit comments

Comments
 (0)