diff --git a/src/apps/review/src/lib/contexts/ChallengeDetailContextProvider.tsx b/src/apps/review/src/lib/contexts/ChallengeDetailContextProvider.tsx index 7e304a5ca..bdc92d857 100644 --- a/src/apps/review/src/lib/contexts/ChallengeDetailContextProvider.tsx +++ b/src/apps/review/src/lib/contexts/ChallengeDetailContextProvider.tsx @@ -5,7 +5,11 @@ import { FC, PropsWithChildren, useContext, useMemo } from 'react' import { useParams } from 'react-router-dom' import { convertBackendSubmissionToSubmissionInfo } from '../models' -import type { ChallengeDetailContextModel, SubmissionInfo } from '../models' +import type { + ChallengeDetailContextModel, + ReviewAppContextModel, + SubmissionInfo, +} from '../models' import { useFetchChallengeInfo, useFetchChallengeInfoProps, @@ -16,11 +20,13 @@ import { } from '../hooks' import { ChallengeDetailContext } from './ChallengeDetailContext' +import { ReviewAppContext } from './ReviewAppContext' export const ChallengeDetailContextProvider: FC = props => { const { challengeId = '' }: { challengeId?: string } = useParams<{ challengeId: string }>() + const { loginUserInfo }: ReviewAppContextModel = useContext(ReviewAppContext) // fetch challenge info const { challengeInfo, @@ -37,10 +43,21 @@ export const ChallengeDetailContextProvider: FC = props => { isLoading: isLoadingChallengeResources, resourceMemberIdMapping, }: useFetchChallengeResourcesProps = useFetchChallengeResources(challengeId) + const submissionViewer = useMemo( + () => ({ + roles: myRoles, + tokenRoles: loginUserInfo?.roles, + userId: loginUserInfo?.userId, + }), + [loginUserInfo?.roles, loginUserInfo?.userId, myRoles], + ) const { challengeSubmissions, isLoading: isLoadingChallengeSubmissions, - }: useFetchChallengeSubmissionsProps = useFetchChallengeSubmissions(challengeId) + }: useFetchChallengeSubmissionsProps = useFetchChallengeSubmissions( + challengeId, + submissionViewer, + ) const submissionInfos = useMemo( () => challengeSubmissions.map(convertBackendSubmissionToSubmissionInfo), diff --git a/src/apps/review/src/lib/hooks/useFetchChallengeSubmissions.ts b/src/apps/review/src/lib/hooks/useFetchChallengeSubmissions.ts index 4c28a8ac0..aa82a69f1 100644 --- a/src/apps/review/src/lib/hooks/useFetchChallengeSubmissions.ts +++ b/src/apps/review/src/lib/hooks/useFetchChallengeSubmissions.ts @@ -5,6 +5,7 @@ import { import useSWR, { SWRResponse } from 'swr' import { handleError } from '~/libs/shared' +import { UserRole } from '~/libs/core' import { BackendSubmission, BackendSubmissionStatus } from '../models' import { fetchSubmissions } from '../services' @@ -22,6 +23,12 @@ interface ChallengeSubmissionsMemoResult { filteredSubmissions: BackendSubmission[] } +export interface ChallengeSubmissionsViewer { + roles?: Array + tokenRoles?: Array + userId?: string | number | null +} + /** * Fetch challenge submissions * @param challengeId challenge id @@ -29,6 +36,7 @@ interface ChallengeSubmissionsMemoResult { */ export function useFetchChallengeSubmissions( challengeId?: string, + viewer?: ChallengeSubmissionsViewer, ): useFetchChallengeSubmissionsProps { // Use swr hooks for submissions fetching const { @@ -46,6 +54,92 @@ export function useFetchChallengeSubmissions( isPaused: () => !challengeId, }) + const normalizedRoles = useMemo( + () => (viewer?.roles ?? []) + .map(role => (role ? `${role}`.toLowerCase() + .trim() : '')) + .filter(Boolean), + [viewer?.roles], + ) + const normalizedTokenRoles = useMemo( + () => (viewer?.tokenRoles ?? []) + .map(role => (typeof role === 'string' ? role.toLowerCase() + .trim() : '')) + .filter(Boolean), + [viewer?.tokenRoles], + ) + const hasSubmitterRole = useMemo( + () => normalizedRoles.some(role => role.includes('submitter')), + [normalizedRoles], + ) + const hasCopilotRole = useMemo( + () => normalizedRoles.some(role => role.includes('copilot')), + [normalizedRoles], + ) + const hasReviewerRole = useMemo( + () => normalizedRoles.some(role => role.includes('reviewer')), + [normalizedRoles], + ) + const hasManagerRole = useMemo( + () => normalizedRoles.some(role => role.includes('manager')), + [normalizedRoles], + ) + const hasScreenerRole = useMemo( + () => normalizedRoles.some(role => role.includes('screener')), + [normalizedRoles], + ) + const hasApproverRole = useMemo( + () => normalizedRoles.some(role => role.includes('approver')), + [normalizedRoles], + ) + const isProjectManager = useMemo( + () => normalizedTokenRoles.some( + role => role === UserRole.projectManager.toLowerCase(), + ) + || normalizedRoles.some(role => role.includes('project manager')), + [normalizedRoles, normalizedTokenRoles], + ) + const isAdmin = useMemo( + () => normalizedTokenRoles.some( + role => role === UserRole.administrator.toLowerCase(), + ) + || normalizedRoles.some(role => role.includes('admin')), + [normalizedRoles, normalizedTokenRoles], + ) + const canViewAllSubmissions = useMemo( + () => (viewer ? ( + isAdmin + || hasCopilotRole + || hasReviewerRole + || hasManagerRole + || hasScreenerRole + || hasApproverRole + || isProjectManager + ) : true), + [ + viewer, + isAdmin, + hasCopilotRole, + hasReviewerRole, + hasManagerRole, + hasScreenerRole, + hasApproverRole, + isProjectManager, + ], + ) + const viewerMemberId = useMemo( + () => { + const raw = viewer?.userId + if (raw === undefined || raw === null) { + return undefined + } + + const normalized = `${raw}`.trim() + return normalized.length ? normalized : undefined + }, + [viewer?.userId], + ) + // Show backend error when fetching data fail useEffect(() => { if (error) { @@ -69,6 +163,10 @@ export function useFetchChallengeSubmissions( const normalizedDeletedIds = new Set() const normalizedDeletedLegacyIds = new Set() const activeSubmissions: BackendSubmission[] = [] + const shouldRestrictToCurrentMember = Boolean( + hasSubmitterRole + && !canViewAllSubmissions, + ) const normalizeStatus = (status: unknown): string => { if (typeof status === 'string') { @@ -105,12 +203,23 @@ export function useFetchChallengeSubmissions( activeSubmissions.push(submission) }) + const visibleSubmissions = shouldRestrictToCurrentMember + ? activeSubmissions.filter(submission => (viewerMemberId + ? `${submission?.memberId ?? ''}` === viewerMemberId + : false)) + : activeSubmissions + return { deletedLegacySubmissionIds: normalizedDeletedLegacyIds, deletedSubmissionIds: normalizedDeletedIds, - filteredSubmissions: activeSubmissions, + filteredSubmissions: visibleSubmissions, } - }, [challengeSubmissions]) + }, [ + challengeSubmissions, + canViewAllSubmissions, + hasSubmitterRole, + viewerMemberId, + ]) return { challengeSubmissions: filteredSubmissions, diff --git a/src/apps/review/src/lib/hooks/useFetchScreeningReview.ts b/src/apps/review/src/lib/hooks/useFetchScreeningReview.ts index 370816480..257cd3ba0 100644 --- a/src/apps/review/src/lib/hooks/useFetchScreeningReview.ts +++ b/src/apps/review/src/lib/hooks/useFetchScreeningReview.ts @@ -436,7 +436,16 @@ export function useFetchScreeningReview(): useFetchScreeningReviewProps { reviewers: challengeReviewers, resources, myResources, + myRoles, }: ChallengeDetailContextModel = useContext(ChallengeDetailContext) + const submissionViewer = useMemo( + () => ({ + roles: myRoles, + tokenRoles: loginUserInfo?.roles, + userId: loginUserInfo?.userId, + }), + [loginUserInfo?.roles, loginUserInfo?.userId, myRoles], + ) const challengeLegacy = (challengeInfo as unknown as { legacy?: { @@ -451,7 +460,10 @@ export function useFetchScreeningReview(): useFetchScreeningReviewProps { deletedLegacySubmissionIds, deletedSubmissionIds, isLoading, - }: useFetchChallengeSubmissionsProps = useFetchChallengeSubmissions(challengeId) + }: useFetchChallengeSubmissionsProps = useFetchChallengeSubmissions( + challengeId, + submissionViewer, + ) const visibleChallengeSubmissions = useMemo( () => challengeSubmissions, diff --git a/start.sh b/start.sh index e1df7e04f..00f210d4f 100644 --- a/start.sh +++ b/start.sh @@ -4,10 +4,9 @@ export ESLINT_NO_DEV_ERRORS=true export HTTPS=true -export HOST=local.topcoder-dev.com -export REACT_APP_HOST_ENV=${REACT_APP_HOST_ENV:-dev} export PORT=443 - +export REACT_APP_HOST_ENV=${REACT_APP_HOST_ENV:-dev} +export HOST=local.topcoder-dev.com # if [[ ! -e ./.environments/.env.local ]]; then # filename=.env.${REACT_APP_HOST_ENV:-dev} # cp ./.environments/$filename ./.environments/.env.local