diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31d2c028..f55e361a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -342,8 +342,8 @@ importers: specifier: ^3.3.4 version: 3.3.4(webpack@5.102.0) tc-auth-lib: - specifier: topcoder-platform/tc-auth-lib#1.0.4 - version: '@topcoder-platform/tc-auth-lib@https://codeload.github.com/topcoder-platform/tc-auth-lib/tar.gz/68fdc22464810c51b703a33e529cdbd6d09437de' + specifier: topcoder-platform/tc-auth-lib#v2.0 + version: '@topcoder-platform/tc-auth-lib@https://codeload.github.com/topcoder-platform/tc-auth-lib/tar.gz/56996006ee5918b3e77fc5a8ab005ae738b4de12' terser: specifier: ^5.31.0 version: 5.44.0 @@ -1518,6 +1518,10 @@ packages: resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} engines: {node: '>= 6'} + '@topcoder-platform/tc-auth-lib@https://codeload.github.com/topcoder-platform/tc-auth-lib/tar.gz/56996006ee5918b3e77fc5a8ab005ae738b4de12': + resolution: {tarball: https://codeload.github.com/topcoder-platform/tc-auth-lib/tar.gz/56996006ee5918b3e77fc5a8ab005ae738b4de12} + version: 1.0.2 + '@topcoder-platform/tc-auth-lib@https://codeload.github.com/topcoder-platform/tc-auth-lib/tar.gz/68fdc22464810c51b703a33e529cdbd6d09437de': resolution: {tarball: https://codeload.github.com/topcoder-platform/tc-auth-lib/tar.gz/68fdc22464810c51b703a33e529cdbd6d09437de} version: 1.0.2 @@ -10189,6 +10193,10 @@ snapshots: '@tootallnate/once@1.1.2': {} + '@topcoder-platform/tc-auth-lib@https://codeload.github.com/topcoder-platform/tc-auth-lib/tar.gz/56996006ee5918b3e77fc5a8ab005ae738b4de12': + dependencies: + lodash: 4.17.21 + '@topcoder-platform/tc-auth-lib@https://codeload.github.com/topcoder-platform/tc-auth-lib/tar.gz/68fdc22464810c51b703a33e529cdbd6d09437de': dependencies: lodash: 4.17.21 diff --git a/src/components/ChallengeEditor/ChallengeReviewer-Field/ChallengeReviewer-Field.module.scss b/src/components/ChallengeEditor/ChallengeReviewer-Field/ChallengeReviewer-Field.module.scss index ca2cf854..268b520e 100644 --- a/src/components/ChallengeEditor/ChallengeReviewer-Field/ChallengeReviewer-Field.module.scss +++ b/src/components/ChallengeEditor/ChallengeReviewer-Field/ChallengeReviewer-Field.module.scss @@ -39,6 +39,10 @@ flex-direction: column; width: 600px; } + + .fieldError { + margin-top: 12px; + } } } diff --git a/src/components/ChallengeEditor/ChallengeReviewer-Field/index.js b/src/components/ChallengeEditor/ChallengeReviewer-Field/index.js index 218e6bf0..5fbafd29 100644 --- a/src/components/ChallengeEditor/ChallengeReviewer-Field/index.js +++ b/src/components/ChallengeEditor/ChallengeReviewer-Field/index.js @@ -482,6 +482,11 @@ class ChallengeReviewerField extends Component { newReviewer.memberReviewerCount = (defaultReviewer && defaultReviewer.memberReviewerCount) || 1 } + // Clear any prior transient error when add succeeds + if (this.state.error) { + this.setState({ error: null }) + } + const updatedReviewers = currentReviewers.concat([newReviewer]) onUpdateReviewers({ field: 'reviewers', value: updatedReviewers }) } @@ -513,6 +518,21 @@ class ChallengeReviewerField extends Component { // Special handling for phase and count changes if (field === 'phaseId') { + // Before changing phase, ensure we're not creating a duplicate manual reviewer for the target phase + const targetPhaseId = value + const isCurrentMember = (updatedReviewers[index] && (updatedReviewers[index].isMemberReview !== false)) + if (isCurrentMember) { + const conflict = (currentReviewers || []).some((r, i) => i !== index && (r.isMemberReview !== false) && (r.phaseId === targetPhaseId)) + if (conflict) { + const phase = (challenge.phases || []).find(p => (p.id === targetPhaseId) || (p.phaseId === targetPhaseId)) + const phaseName = phase ? (phase.name || targetPhaseId) : targetPhaseId + this.setState({ + error: `Cannot move manual reviewer to phase '${phaseName}' because a manual reviewer configuration already exists for that phase.` + }) + return + } + } + this.handlePhaseChangeWithReassign(index, value) // update payment based on default reviewer @@ -632,6 +652,21 @@ class ChallengeReviewerField extends Component { const currentReviewers = challenge.reviewers || [] const updatedReviewers = currentReviewers.slice() + // Block switching an AI reviewer to a member reviewer if another manual reviewer exists for same phase + if (!isAI) { + const existingReviewer = currentReviewers[index] || {} + const phaseId = existingReviewer.phaseId + const conflict = currentReviewers.some((r, i) => i !== index && (r.isMemberReview !== false) && (r.phaseId === phaseId)) + if (conflict) { + const phase = (challenge.phases || []).find(p => (p.id === phaseId) || (p.phaseId === phaseId)) + const phaseName = phase ? (phase.name || phaseId) : phaseId + this.setState({ + error: `Cannot switch to Member Reviewer: a manual reviewer configuration already exists for phase '${phaseName}'. Increase "Number of Reviewers" on the existing configuration instead.` + }) + return + } + } + // Update reviewer type by setting/clearing aiWorkflowId const currentReviewer = updatedReviewers[index] @@ -674,6 +709,11 @@ class ChallengeReviewerField extends Component { this.handleToggleShouldOpen(index, true) } + // Clear any transient error when successful change is applied + if (this.state.error) { + this.setState({ error: null }) + } + onUpdateReviewers({ field: 'reviewers', value: updatedReviewers }) }} > @@ -772,10 +812,10 @@ class ChallengeReviewerField extends Component { const isPostMortemPhase = norm === 'postmortem' const isCurrentlySelected = reviewer.phaseId && ((phase.id === reviewer.phaseId) || (phase.phaseId === reviewer.phaseId)) && !isSubmissionPhase - // Collect phases already assigned to other reviewers (excluding current reviewer) + // Collect phases already assigned to other manual (member) reviewers (excluding current reviewer) const assignedPhaseIds = new Set( (challenge.reviewers || []) - .filter((r, i) => i !== index) + .filter((r, i) => i !== index && (r.isMemberReview !== false)) .map(r => r.phaseId) .filter(id => id !== undefined && id !== null) ) @@ -1051,6 +1091,11 @@ class ChallengeReviewerField extends Component { /> )} + {error && !isLoading && ( +