Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ workflows:
context: org-global
filters: &filters-dev
branches:
only: ["develop", "PM-803_wm-regression-fixes", "PM-902_show-all-projects-on-challenge-page", "pm-1355_1"]
only: ["develop", "PM-803_wm-regression-fixes", "PM-902_show-all-projects-on-challenge-page", "pm-1365"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The branch name 'pm-1365' should be checked for consistency with the naming conventions used in your project. Ensure that it follows the same pattern as other branch names, such as using uppercase letters or underscores if applicable.


# Production builds are exectuted only on tagged commits to the
# master branch.
Expand Down
2 changes: 1 addition & 1 deletion src/components/ChallengesComponent/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const ChallengesComponent = ({
<OutlineButton
text='Request Copilot'
type={'info'}
url={`${COPILOTS_URL}/requests/new`}
url={`${COPILOTS_URL}/requests/new?projectId=${activeProject.id}`}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that activeProject is always defined and has an id property before using it in the URL. Consider adding validation or error handling to prevent potential runtime errors if activeProject is undefined or does not have an id.

target={'_blank'}
/>
)}
Expand Down
11 changes: 6 additions & 5 deletions src/components/Users/Users.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@
}
}

.textContent {
font-size: 18px;
margin-top: 25px;
}


.row {
display: flex;
Expand Down Expand Up @@ -400,7 +405,7 @@
justify-content: center;
}

.addButtonContainer {
.addButtonContainer, .buttonWrapper {
display: flex;
justify-content: flex-start;
height: 30px;
Expand All @@ -412,10 +417,6 @@
}
}

.addUserContentContainer {

}

.tcRadioButton {
.tc-radioButton-label {
@include roboto-light();
Expand Down
3 changes: 3 additions & 0 deletions src/components/Users/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ class Users extends Component {
addNewProjectMember={this.props.addNewProjectMember}
onMemberInvited={this.props.addNewProjectInvite}
onClose={this.resetAddUserState}
projectOption={this.state.projectOption}
projectMembers={projectMembers}
updateProjectMember={updateProjectMember}
/>
)
}
Expand Down
239 changes: 142 additions & 97 deletions src/components/Users/user-add.modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,30 @@ import Modal from '../Modal'
import SelectUserAutocomplete from '../SelectUserAutocomplete'
import { PROJECT_ROLES } from '../../config/constants'
import PrimaryButton from '../Buttons/PrimaryButton'
import { addUserToProject, inviteUserToProject } from '../../services/projects'
import { addUserToProject, inviteUserToProject, updateProjectMemberRole } from '../../services/projects'

import styles from './Users.module.scss'

const theme = {
container: styles.modalContainer
}

const UserAddModalContent = ({ projectId, addNewProjectMember, onMemberInvited, onClose }) => {
const UserAddModalContent = ({
projectMembers,
projectOption,
projectId,
addNewProjectMember,
onMemberInvited,
onClose,
updateProjectMember
}) => {
const [userToAdd, setUserToAdd] = useState(null)
const [userPermissionToAdd, setUserPermissionToAdd] = useState(PROJECT_ROLES.READ)
const [showSelectUserError, setShowSelectUserError] = useState(false)
const [addUserError, setAddUserError] = useState(null)
const [isAdding, setIsAdding] = useState(false)
const [isUserAddingFailed, setUserAddingFailed] = useState(false)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider renaming isUserAddingFailed to hasUserAddingFailed for better readability and to follow boolean naming conventions.

const [existingRole, setExistingRole] = useState('')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existingRole state is introduced but not used in the provided diff. Ensure that it is utilized appropriately or remove it if unnecessary.


const onUpdateUserToAdd = (option) => {
if (option && option.value) {
Expand Down Expand Up @@ -52,8 +62,12 @@ const UserAddModalContent = ({ projectId, addNewProjectMember, onMemberInvited,
})
if (failed) {
const error = get(failed, '0.message', 'User cannot be invited')
const errorCode = get(failed, '0.error')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider checking if failed is an array and has elements before accessing failed[0] to avoid potential runtime errors.

const role = get(failed, '0.role')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider checking if failed is an array and has elements before accessing failed[0] to avoid potential runtime errors.

setAddUserError(error)
setIsAdding(false)
setUserAddingFailed(errorCode === 'ALREADY_MEMBER')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that errorCode is always defined before comparing it to 'ALREADY_MEMBER' to prevent unexpected behavior.

setExistingRole(role)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that role is always defined before using it to prevent unexpected behavior.

} else if (rest.message) {
setAddUserError(rest.message)
setIsAdding(false)
Expand All @@ -74,121 +88,152 @@ const UserAddModalContent = ({ projectId, addNewProjectMember, onMemberInvited,
}
}

const onConfirmCopilotRoleChange = async () => {
const member = projectMembers.find(item => item.userId === userToAdd.userId)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider checking if member is undefined before accessing its properties to avoid potential runtime errors.

const action = member.role === 'manager' ? 'complete-copilot-requests' : ''
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for determining the action string could be more explicit. Consider using a switch statement or a more descriptive conditional structure for clarity.

const response = await updateProjectMemberRole(projectId, member.id, 'copilot', action)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle potential errors from the updateProjectMemberRole function call. Consider using a try-catch block to manage exceptions and provide feedback to the user if the update fails.

updateProjectMember(response)
onClose()
}

const onCancelCopilotRoleChange = () => {
setUserAddingFailed(false)
setAddUserError('')
}

return (
<Modal theme={theme} onCancel={onClose}>
<div className={cn(styles.contentContainer, styles.confirm)}>
<div className={styles.title}>Add User</div>
<div className={styles.addUserContentContainer}>
<div className={styles.row}>
<div className={cn(styles.field, styles.col1, styles.addUserTitle)}>
Member<span className={styles.required}>*</span> :
</div>
<div className={cn(styles.field, styles.col2)}>
<SelectUserAutocomplete
value={userToAdd ? { label: userToAdd.handle, value: userToAdd.userId.toString() } : null}
onChange={onUpdateUserToAdd}
/>
{
isUserAddingFailed && (['observer', 'customer', 'copilot', 'manager'].includes(existingRole)) && (
<div className={cn(styles.contentContainer, styles.confirm)}>
<div className={styles.textContent}>{`The copilot ${userToAdd.handle} is part of ${projectOption.label} project with ${existingRole} role.`}</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that userToAdd and projectOption are defined before accessing their properties to prevent potential errors.

<div className={styles.buttonWrapper}>
<PrimaryButton onClick={onConfirmCopilotRoleChange} text={'Confirm'} type={'info'} />
<PrimaryButton onClick={onCancelCopilotRoleChange} text={'Cancel'} type={'disabled'} />
</div>
</div>
{showSelectUserError && (
<div className={styles.row}>
<div className={styles.errorMesssage}>Please select a member.</div>
</div>
)}
<div className={styles.row}>
<div className={cn(styles.field, styles.col1, styles.addUserTitle)}>
<label htmlFor='memberToAdd'>Role :</label>
</div>
<div className={cn(styles.col5)}>
<div className={styles.tcRadioButton}>
<input
name={`add-user-radio`}
type='radio'
id={`read-add-user`}
checked={userPermissionToAdd === PROJECT_ROLES.READ}
onChange={() => setUserPermissionToAdd(PROJECT_ROLES.READ)}
/>
<label htmlFor={`read-add-user`}>
<div>Read</div>
<input type='hidden' />
</label>
)
}
{
!isUserAddingFailed && (
<div className={cn(styles.contentContainer, styles.confirm)}>
<div className={styles.title}>Add User</div>
<div className={styles.addUserContentContainer}>
<div className={styles.row}>
<div className={cn(styles.field, styles.col1, styles.addUserTitle)}>
Member<span className={styles.required}>*</span> :
</div>
<div className={cn(styles.field, styles.col2)}>
<SelectUserAutocomplete
value={userToAdd ? { label: userToAdd.handle, value: userToAdd.userId.toString() } : null}
onChange={onUpdateUserToAdd}
/>
</div>
</div>
</div>
<div className={cn(styles.col5)}>
<div className={styles.tcRadioButton}>
<input
name={`add-user-radio`}
type='radio'
id={`write-add-user`}
checked={userPermissionToAdd === PROJECT_ROLES.WRITE}
onChange={() => setUserPermissionToAdd(PROJECT_ROLES.WRITE)}
/>
<label htmlFor={`write-add-user`}>
<div>Write</div>
<input type='hidden' />
</label>
{showSelectUserError && (
<div className={styles.row}>
<div className={styles.errorMesssage}>Please select a member.</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a typo in the class name styles.errorMesssage. It should be styles.errorMessage.

</div>
)}
<div className={styles.row}>
<div className={cn(styles.field, styles.col1, styles.addUserTitle)}>
<label htmlFor='memberToAdd'>Role :</label>
</div>
<div className={cn(styles.col5)}>
<div className={styles.tcRadioButton}>
<input
name={`add-user-radio`}
type='radio'
id={`read-add-user`}
checked={userPermissionToAdd === PROJECT_ROLES.READ}
onChange={() => setUserPermissionToAdd(PROJECT_ROLES.READ)}
/>
<label htmlFor={`read-add-user`}>
<div>Read</div>
<input type='hidden' />
</label>
</div>
</div>
<div className={cn(styles.col5)}>
<div className={styles.tcRadioButton}>
<input
name={`add-user-radio`}
type='radio'
id={`write-add-user`}
checked={userPermissionToAdd === PROJECT_ROLES.WRITE}
onChange={() => setUserPermissionToAdd(PROJECT_ROLES.WRITE)}
/>
<label htmlFor={`write-add-user`}>
<div>Write</div>
<input type='hidden' />
</label>
</div>
</div>
<div className={cn(styles.col5)}>
<div className={styles.tcRadioButton}>
<input
name={`add-user-radio`}
type='radio'
id={`full-access-add-user`}
checked={userPermissionToAdd === PROJECT_ROLES.MANAGER}
onChange={() => setUserPermissionToAdd(PROJECT_ROLES.MANAGER)}
/>
<label htmlFor={`full-access-add-user`}>
<div>Full Access</div>
<input type='hidden' />
</label>
</div>
</div>
<div className={cn(styles.col5)}>
<div className={styles.tcRadioButton}>
<input
name={`add-user-radio`}
type='radio'
id={`copilot-add-user`}
checked={userPermissionToAdd === PROJECT_ROLES.COPILOT}
onChange={() => setUserPermissionToAdd(PROJECT_ROLES.COPILOT)}
/>
<label htmlFor={`copilot-add-user`}>
<div>Copilot</div>
<input type='hidden' />
</label>
</div>
</div>
</div>
{addUserError && (
<div className={styles.errorMesssage}>{addUserError}</div>
)}
</div>
<div className={cn(styles.col5)}>
<div className={styles.tcRadioButton}>
<input
name={`add-user-radio`}
type='radio'
id={`full-access-add-user`}
checked={userPermissionToAdd === PROJECT_ROLES.MANAGER}
onChange={() => setUserPermissionToAdd(PROJECT_ROLES.MANAGER)}
<div className={styles.buttonGroup}>
<div className={styles.buttonSizeA}>
<PrimaryButton
text={'Close'}
type={'info'}
onClick={onClose}
/>
<label htmlFor={`full-access-add-user`}>
<div>Full Access</div>
<input type='hidden' />
</label>
</div>
</div>
<div className={cn(styles.col5)}>
<div className={styles.tcRadioButton}>
<input
name={`add-user-radio`}
type='radio'
id={`copilot-add-user`}
checked={userPermissionToAdd === PROJECT_ROLES.COPILOT}
onChange={() => setUserPermissionToAdd(PROJECT_ROLES.COPILOT)}
<div className={styles.buttonSizeA}>
<PrimaryButton
text={isAdding ? 'Adding user...' : 'Add User'}
type={'info'}
onClick={onAddUserConfirmClick}
/>
<label htmlFor={`copilot-add-user`}>
<div>Copilot</div>
<input type='hidden' />
</label>
</div>
</div>
</div>
{addUserError && (
<div className={styles.errorMesssage}>{addUserError}</div>
)}
</div>
<div className={styles.buttonGroup}>
<div className={styles.buttonSizeA}>
<PrimaryButton
text={'Close'}
type={'info'}
onClick={onClose}
/>
</div>
<div className={styles.buttonSizeA}>
<PrimaryButton
text={isAdding ? 'Adding user...' : 'Add User'}
type={'info'}
onClick={onAddUserConfirmClick}
/>
</div>
</div>
</div>
)
}
</Modal>
)
}
UserAddModalContent.propTypes = {
projectId: PropTypes.number.isRequired,
addNewProjectMember: PropTypes.func.isRequired,
onMemberInvited: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired
onClose: PropTypes.func.isRequired,
projectOption: PropTypes.any.isRequired,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider specifying a more precise PropType for projectOption instead of PropTypes.any to improve type safety and maintainability.

projectMembers: PropTypes.array.isRequired,
updateProjectMember: PropTypes.func.isRequired
}

export default UserAddModalContent
2 changes: 1 addition & 1 deletion src/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ export const GROUPS_DROPDOWN_PER_PAGE = 1000000 // make sure we are getting all
/**
* The list of challenge types which can have multiple prizes
*/
export const CHALLENGE_TYPES_WITH_MULTIPLE_PRIZES = ['Challenge']
export const CHALLENGE_TYPES_WITH_MULTIPLE_PRIZES = ['Challenge', 'Marathon Match']

/**
* All the repeating messages.
Expand Down
26 changes: 16 additions & 10 deletions src/containers/ProjectInvitations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,22 @@ const ProjectInvitations = ({ match, auth, isProjectLoading, history, projectDet

const updateInvite = useCallback(async (status, source) => {
setIsUpdating(status)
await updateProjectMemberInvite(projectId, invitation.id, status, source)

// await for the project details to propagate
await delay(1000)
await loadProjectInvites(projectId)
toastr.success('Success', `Successfully ${status} the invitation.`)

// await for the project details to fetch
await delay(1000)
history.push(status === PROJECT_MEMBER_INVITE_STATUS_ACCEPTED ? `/projects/${projectId}/challenges` : '/projects')
try {
await updateProjectMemberInvite(projectId, invitation.id, status, source)

// await for the project details to propagate
await delay(1000)
await loadProjectInvites(projectId)
toastr.success('Success', `Successfully ${status} the invitation.`)

// await for the project details to fetch
await delay(1000)
history.push(status === PROJECT_MEMBER_INVITE_STATUS_ACCEPTED ? `/projects/${projectId}/challenges` : '/projects')
} catch (e) {
toastr.error('Error', e.response.data.message)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider checking if e.response and e.response.data are defined before accessing e.response.data.message to avoid potential runtime errors.

await delay(1000)
history.push('/projects')
}
}, [projectId, invitation, loadProjectInvites, history])

const acceptInvite = useCallback(() => updateInvite(PROJECT_MEMBER_INVITE_STATUS_ACCEPTED, source), [updateInvite, source])
Expand Down
Loading