From f5b102e7004e67143a02a4533c7476b6ced74677 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 4 Mar 2026 19:51:39 +0100 Subject: [PATCH 01/18] PM-4079 #time 4h converted reviewers into row based table --- .../TableAppeals/TableAppeals.module.scss | 1 + .../components/TableAppeals/TableAppeals.tsx | 144 ++++++------ .../TableAppealsResponse.module.scss | 1 + .../TableAppealsResponse.tsx | 216 +++++++++--------- .../TableReview/TableReview.module.scss | 1 + .../components/TableReview/TableReview.tsx | 186 +++++++-------- .../review/src/lib/components/common/types.ts | 40 ++++ 7 files changed, 304 insertions(+), 285 deletions(-) diff --git a/src/apps/review/src/lib/components/TableAppeals/TableAppeals.module.scss b/src/apps/review/src/lib/components/TableAppeals/TableAppeals.module.scss index c65ae7124..7ea3ce4a5 100644 --- a/src/apps/review/src/lib/components/TableAppeals/TableAppeals.module.scss +++ b/src/apps/review/src/lib/components/TableAppeals/TableAppeals.module.scss @@ -55,6 +55,7 @@ white-space: nowrap; } + .mobileLabel { display: inline-block; font-weight: 600; diff --git a/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx b/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx index a4415fa77..3dd8ba068 100644 --- a/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx +++ b/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx @@ -43,7 +43,9 @@ import type { DownloadButtonConfig, ScoreVisibilityConfig, SubmissionRow, + SubmissionReviewerRow, } from '../common/types' +import { buildSubmissionReviewerRows } from '../common/types' import type { AggregatedSubmissionReviews } from '../../utils/aggregateSubmissionReviews' import { CollapsibleAiReviewsRow } from '../CollapsibleAiReviewsRow' @@ -209,15 +211,9 @@ export const TableAppeals: FC = (props: TableAppealsProps) => restrictToLatest, ]) - const maxReviewCount = useMemo( - () => aggregatedResults.reduce( - (maxCount, result) => { - const reviewCount = result.reviews?.length ?? 0 - return reviewCount > maxCount ? reviewCount : maxCount - }, - 0, - ), - [aggregatedResults], + const reviewerRows = useMemo( + () => buildSubmissionReviewerRows(aggregatedRows), + [aggregatedRows], ) const { canViewAllSubmissions }: UseRolePermissionsResult = useRolePermissions() @@ -302,95 +298,89 @@ export const TableAppeals: FC = (props: TableAppealsProps) => ], ) - const columns = useMemo[]>(() => { - const submissionIdColumn: TableColumn = { + const columns = useMemo[]>(() => { + const submissionIdColumn: TableColumn = { className: styles.submissionColumn, columnId: 'submission-id', label: 'Submission ID', propertyName: 'id', - renderer: (submission: SubmissionRow) => renderSubmissionIdCell( - submission, - downloadButtonConfig, + renderer: (row: SubmissionReviewerRow) => ( + row.isFirstReviewerRow + ? renderSubmissionIdCell(row, downloadButtonConfig) + : ), type: 'element', } - const baseColumns: TableColumn[] = [submissionIdColumn] + const baseColumns: TableColumn[] = [submissionIdColumn] if (!hideHandleColumn) { baseColumns.push({ columnId: 'handle-aggregated', label: 'Submitter', propertyName: 'handle', - renderer: renderSubmitterHandleCell, + renderer: (row: SubmissionReviewerRow) => ( + row.isFirstReviewerRow + ? renderSubmitterHandleCell(row) + : + ), type: 'element', }) } - baseColumns.push({ - columnId: 'review-date', - label: 'Review Date', - renderer: renderReviewDateCell, - type: 'element', - }) - if (shouldShowAggregatedReviewScore) { baseColumns.push({ columnId: 'review-score', label: 'Review Score', - renderer: (submission: SubmissionRow) => renderReviewScoreCell( - submission, - scoreVisibilityConfig, + renderer: (row: SubmissionReviewerRow) => ( + row.isFirstReviewerRow + ? renderReviewScoreCell(row, scoreVisibilityConfig) + : ), type: 'element', }) } - for (let index = 0; index < maxReviewCount; index += 1) { - const reviewerLabel = maxReviewCount === 1 - ? 'Reviewer' - : `Reviewer ${index + 1}` - const scoreLabel = maxReviewCount === 1 - ? 'Score' - : `Score ${index + 1}` - const appealsLabel = maxReviewCount === 1 - ? 'Appeals' - : `Appeals ${index + 1}` - - baseColumns.push({ - columnId: `reviewer-${index}`, - label: reviewerLabel, - renderer: (submission: SubmissionRow) => renderReviewerCell( - submission, - index, + baseColumns.push( + { + columnId: 'reviewer', + label: 'Reviewer', + renderer: (row: SubmissionReviewerRow) => renderReviewerCell( + row, + row.reviewerIndex, ), type: 'element', - }) + }, + { + columnId: 'review-date', + label: 'Review Date', + renderer: (row: SubmissionReviewerRow) => renderReviewDateCell(row), + type: 'element', + }, + { + columnId: 'score', + label: 'Score', + renderer: (row: SubmissionReviewerRow) => renderScoreCell( + row, + row.reviewerIndex, + scoreVisibilityConfig, + ), + type: 'element', + }, + ) + if (allowsAppeals) { baseColumns.push({ - columnId: `score-${index}`, - label: scoreLabel, - renderer: (submission: SubmissionRow) => renderScoreCell( - submission, - index, + className: styles.tableCellNoWrap, + columnId: 'appeals', + label: 'Appeals', + renderer: (row: SubmissionReviewerRow) => renderAppealsCell( + row, + row.reviewerIndex, scoreVisibilityConfig, ), type: 'element', }) - - if (allowsAppeals) { - baseColumns.push({ - className: styles.tableCellNoWrap, - columnId: `appeals-${index}`, - label: appealsLabel, - renderer: (submission: SubmissionRow) => renderAppealsCell( - submission, - index, - scoreVisibilityConfig, - ), - type: 'element', - }) - } } if (props.aiReviewers) { @@ -398,16 +388,25 @@ export const TableAppeals: FC = (props: TableAppealsProps) => columnId: 'ai-reviews-table', isExpand: true, label: '', - renderer: (submission: SubmissionRow, allRows: SubmissionRow[]) => ( - props.aiReviewers && ( + renderer: (row: SubmissionReviewerRow, allRows: SubmissionReviewerRow[]) => { + if (!row.isLastReviewerRow || !props.aiReviewers) { + return + } + + const firstIndexForSubmission = allRows.findIndex(candidate => ( + candidate.id === row.id && candidate.isFirstReviewerRow + )) + const defaultOpen = firstIndexForSubmission === 0 + + return ( ) - ), + }, type: 'element', }) } @@ -417,12 +416,11 @@ export const TableAppeals: FC = (props: TableAppealsProps) => allowsAppeals, downloadButtonConfig, hideHandleColumn, - maxReviewCount, scoreVisibilityConfig, shouldShowAggregatedReviewScore, ]) - const columnsMobile = useMemo[][]>( + const columnsMobile = useMemo[][]>( () => columns.map(column => ([ column.label && { columnId: `${column.columnId}-label`, @@ -444,7 +442,7 @@ export const TableAppeals: FC = (props: TableAppealsProps) => label: '', mobileType: 'last-value', }, - ]).filter(Boolean) as MobileTableColumn[]), + ]).filter(Boolean) as MobileTableColumn[]), [columns], ) @@ -459,11 +457,11 @@ export const TableAppeals: FC = (props: TableAppealsProps) => )} > {isTablet ? ( - + ) : ( = (props: Table submitterCanViewAllRows, ]) - const maxReviewCount = useMemo( - () => visibleRows.reduce( - (maxCount, row) => { - const reviewCount = row.aggregated?.reviews?.length ?? 0 - return reviewCount > maxCount ? reviewCount : maxCount - }, - 0, - ), + const reviewerRows = useMemo( + () => buildSubmissionReviewerRows(visibleRows), [visibleRows], ) @@ -326,129 +322,122 @@ export const TableAppealsResponse: FC = (props: Table [canRespondToAppeals], ) - const columns = useMemo[]>(() => { - const submissionIdColumn: TableColumn = { + const columns = useMemo[]>(() => { + const submissionIdColumn: TableColumn = { className: styles.submissionColumn, columnId: 'submission-id', label: 'Submission ID', propertyName: 'id', - renderer: (submission: SubmissionRow) => renderSubmissionIdCell( - submission, - downloadButtonConfig, + renderer: (row: SubmissionReviewerRow) => ( + row.isFirstReviewerRow + ? renderSubmissionIdCell(row, downloadButtonConfig) + : ), type: 'element', } - const baseColumns: TableColumn[] = [submissionIdColumn] + const baseColumns: TableColumn[] = [submissionIdColumn] if (!hideHandleColumn) { baseColumns.push({ columnId: 'handle-aggregated', label: 'Submitter', propertyName: 'handle', - renderer: renderSubmitterHandleCell, + renderer: (row: SubmissionReviewerRow) => ( + row.isFirstReviewerRow + ? renderSubmitterHandleCell(row) + : + ), type: 'element', }) } + baseColumns.push({ + columnId: 'review-score', + label: 'Review Score', + renderer: (row: SubmissionReviewerRow) => ( + row.isFirstReviewerRow + ? renderReviewScoreCell(row, scoreVisibilityConfig) + : + ), + type: 'element', + }) + baseColumns.push( + { + columnId: 'reviewer', + label: 'Reviewer', + renderer: (row: SubmissionReviewerRow) => renderReviewerCell( + row, + row.reviewerIndex, + ), + type: 'element', + }, { columnId: 'review-date', label: 'Review Date', - renderer: renderReviewDateCell, + renderer: (row: SubmissionReviewerRow) => renderReviewDateCell(row), type: 'element', }, { - columnId: 'review-score', - label: 'Review Score', - renderer: (submission: SubmissionRow) => renderReviewScoreCell( - submission, + columnId: 'score', + label: 'Score', + renderer: (row: SubmissionReviewerRow) => renderScoreCell( + row, + row.reviewerIndex, scoreVisibilityConfig, ), type: 'element', }, ) - for (let index = 0; index < maxReviewCount; index += 1) { - const reviewerLabel = maxReviewCount === 1 ? 'Reviewer' : `Reviewer ${index + 1}` - const scoreLabel = maxReviewCount === 1 ? 'Score' : `Score ${index + 1}` - const appealsLabel = maxReviewCount === 1 ? 'Appeals' : `Appeals ${index + 1}` - const remainingLabel = maxReviewCount === 1 ? 'Remaining' : `Remaining ${index + 1}` - - baseColumns.push({ - columnId: `reviewer-${index}`, - label: reviewerLabel, - renderer: (submission: SubmissionRow) => renderReviewerCell( - submission, - index, - ), - type: 'element', - }) - - baseColumns.push({ - columnId: `score-${index}`, - label: scoreLabel, - renderer: (submission: SubmissionRow) => renderScoreCell( - submission, - index, - scoreVisibilityConfig, - ), - type: 'element', - }) - - if (allowsAppeals) { - baseColumns.push({ + if (allowsAppeals) { + baseColumns.push( + { className: styles.tableCellNoWrap, - columnId: `appeals-${index}`, - label: appealsLabel, - renderer: (submission: SubmissionRow) => renderAppealsCell( - submission, - index, + columnId: 'appeals', + label: 'Appeals', + renderer: (row: SubmissionReviewerRow) => renderAppealsCell( + row, + row.reviewerIndex, scoreVisibilityConfig, ), type: 'element', - }) - - baseColumns.push({ + }, + { className: styles.tableCellNoWrap, - columnId: `remaining-${index}`, - label: remainingLabel, - renderer: (submission: SubmissionRow) => renderRemainingCell( - submission, - index, + columnId: 'remaining', + label: 'Remaining', + renderer: (row: SubmissionReviewerRow) => renderRemainingCell( + row, + row.reviewerIndex, ), type: 'element', - }) - } + }, + ) } if (isAppealsResponsePhaseOpen && canRespondToAppeals) { baseColumns.push({ columnId: 'actions', label: 'Actions', - renderer: (submission: SubmissionRow) => { - const reviews = submission.aggregated?.reviews ?? [] + renderer: (row: SubmissionReviewerRow) => { + const reviewDetail = row.aggregated?.reviews?.[row.reviewerIndex] + const reviewId = reviewDetail?.reviewInfo?.id ?? reviewDetail?.reviewId - const actionableReviews = reviews - .map(review => { - const reviewId = review.reviewInfo?.id ?? review.reviewId - if (!reviewId) { - return undefined - } - - const totalAppeals = review.totalAppeals ?? 0 - const finishedAppeals = review.finishedAppeals ?? 0 - const remaining = Math.max(totalAppeals - finishedAppeals, 0) - - if (remaining <= 0) { - return undefined - } + if (!reviewDetail || !reviewId) { + return ( + + -- + + ) + } - return reviewId - }) - .filter((reviewId): reviewId is string => Boolean(reviewId)) + const totalAppeals = reviewDetail.totalAppeals ?? 0 + const finishedAppeals = reviewDetail.finishedAppeals ?? 0 + const remaining = Math.max(totalAppeals - finishedAppeals, 0) - if (!actionableReviews.length) { + if (remaining <= 0) { return ( -- @@ -458,22 +447,19 @@ export const TableAppealsResponse: FC = (props: Table return ( - {actionableReviews.map((reviewId, index, array) => ( - + - - Respond to Appeals - - - ))} + Respond to Appeals + + ) }, @@ -486,16 +472,25 @@ export const TableAppealsResponse: FC = (props: Table columnId: 'ai-reviews-table', isExpand: true, label: '', - renderer: (submission: SubmissionRow, allRows: SubmissionRow[]) => ( - props.aiReviewers && ( + renderer: (row: SubmissionReviewerRow, allRows: SubmissionReviewerRow[]) => { + if (!row.isLastReviewerRow || !props.aiReviewers) { + return + } + + const firstIndexForSubmission = allRows.findIndex(candidate => ( + candidate.id === row.id && candidate.isFirstReviewerRow + )) + const defaultOpen = firstIndexForSubmission === 0 + + return ( ) - ), + }, type: 'element', }) } @@ -507,17 +502,16 @@ export const TableAppealsResponse: FC = (props: Table hideHandleColumn, canRespondToAppeals, isAppealsResponsePhaseOpen, - maxReviewCount, scoreVisibilityConfig, ]) - const columnsMobile = useMemo[][]>( + const columnsMobile = useMemo[][]>( () => columns.map(column => { const label = typeof column.label === 'function' ? column.label() : column.label ?? '' - const labelColumn: MobileTableColumn = { + const labelColumn: MobileTableColumn = { columnId: `${column.columnId}-label`, label: '', mobileType: 'label', @@ -529,7 +523,7 @@ export const TableAppealsResponse: FC = (props: Table type: 'element', } - const valueColumn: MobileTableColumn = { + const valueColumn: MobileTableColumn = { ...column, colSpan: label ? 1 : 2, columnId: `${column.columnId}-value`, @@ -538,7 +532,7 @@ export const TableAppealsResponse: FC = (props: Table ...(column.columnId === 'actions' ? { colSpan: 2 } : {}), } - return [!!label && labelColumn, valueColumn].filter(Boolean) as MobileTableColumn[] + return [!!label && labelColumn, valueColumn].filter(Boolean) as MobileTableColumn[] }), [columns], ) @@ -561,13 +555,13 @@ export const TableAppealsResponse: FC = (props: Table )} > {isTablet ? ( - + ) : (
= (props: TableReviewProps) => { return rows.filter(row => row.id && latestSubmissionIds.has(row.id)) }, [aggregatedSubmissionRows, latestSubmissionIds, restrictToLatest]) - const maxReviewCount = useMemo( - () => aggregatedSubmissionRows.reduce( - (max, aggregated) => Math.max(max, aggregated.reviews?.length ?? 0), - 0, - ), - [aggregatedSubmissionRows], + const reviewerRows = useMemo( + () => buildSubmissionReviewerRows(aggregatedRows), + [aggregatedRows], ) - interface ReviewerColumnMetadata { - label: string - renderLabel?: () => JSX.Element - } - const reviewerColumnMetadata = useMemo(() => ( - Array.from({ length: maxReviewCount }, (_unused, index) => ({ - label: `Reviewer ${index + 1}`, - })) - ), [maxReviewCount]) const [isReopening, setIsReopening] = useState(false) const [pendingReopen, setPendingReopen] = useState(undefined) @@ -436,7 +426,9 @@ export const TableReview: FC = (props: TableReviewProps) => { ], ) - const renderActionsCell = useCallback<(submission: SubmissionRow) => JSX.Element>((submission: SubmissionRow) => { + const renderActionsCell = useCallback<(submission: SubmissionReviewerRow) => JSX.Element>(( + submission: SubmissionReviewerRow, + ) => { const reviews = submission.aggregated?.reviews ?? [] const myReviewDetail = reviews.find(review => { const resourceId = review.reviewInfo?.resourceId ?? review.resourceId @@ -616,78 +608,86 @@ export const TableReview: FC = (props: TableReviewProps) => { shouldShowHistoryActions, ]) - const columns = useMemo[]>(() => { - const submissionIdColumn: TableColumn = { + const columns = useMemo[]>(() => { + const submissionIdColumn: TableColumn = { className: styles.submissionColumn, columnId: 'submission-id', label: 'Submission ID', propertyName: 'id', - renderer: (submission: SubmissionRow) => renderSubmissionIdCell( - submission, - downloadButtonConfig, + renderer: (row: SubmissionReviewerRow) => ( + row.isFirstReviewerRow + ? renderSubmissionIdCell(row, downloadButtonConfig) + : ), type: 'element', } - const baseColumns: TableColumn[] = [submissionIdColumn] + const baseColumns: TableColumn[] = [submissionIdColumn] if (!hideHandleColumn) { baseColumns.push({ columnId: 'handle-aggregated', label: 'Submitter', propertyName: 'handle', - renderer: renderSubmitterHandleCell, + renderer: (row: SubmissionReviewerRow) => ( + row.isFirstReviewerRow + ? renderSubmitterHandleCell(row) + : + ), type: 'element', }) } - for (let index = 0; index < maxReviewCount; index += 1) { - const metadata = reviewerColumnMetadata[index] ?? { - label: `Reviewer ${index + 1}`, - } - baseColumns.push( - { - columnId: `reviewer-${index}`, - label: metadata.renderLabel ?? metadata.label, - renderer: (submission: SubmissionRow) => renderReviewerCell(submission, index), - type: 'element', - }, - { - columnId: `score-${index}`, - label: `Score ${index + 1}`, - renderer: (submission: SubmissionRow) => renderScoreCell( - submission, - index, - scoreVisibilityConfig, - challengeInfo, - pendingReopen, - canManageCompletedReviews, - isReopening, - openReopenDialog, - ), - type: 'element', - }, - ) - } - baseColumns.push( + { + columnId: 'reviewer', + label: 'Reviewer', + renderer: (row: SubmissionReviewerRow) => renderReviewerCell( + row, + row.reviewerIndex, + ), + type: 'element', + }, { columnId: 'review-date', label: 'Review Date', - renderer: renderReviewDateCell, + renderer: (row: SubmissionReviewerRow) => renderReviewDateCell(row), type: 'element', }, { columnId: 'review-score', label: 'Review Score', - renderer: (submission: SubmissionRow) => renderReviewScoreCell(submission, scoreVisibilityConfig), + renderer: (row: SubmissionReviewerRow) => ( + row.isFirstReviewerRow + ? renderReviewScoreCell(row, scoreVisibilityConfig) + : + ), + type: 'element', + }, + { + columnId: 'score', + label: 'Score', + renderer: (row: SubmissionReviewerRow) => renderScoreCell( + row, + row.reviewerIndex, + scoreVisibilityConfig, + challengeInfo, + pendingReopen, + canManageCompletedReviews, + isReopening, + openReopenDialog, + ), type: 'element', }, { columnId: 'review-result', label: 'Review Result', - renderer: (submission: SubmissionRow) => { - const result = resolveSubmissionReviewResult(submission, { + renderer: (row: SubmissionReviewerRow) => { + if (!row.isFirstReviewerRow) { + return + } + + const result = resolveSubmissionReviewResult(row, { minimumPassingScoreByScorecardId, }) if (result === 'PASS') { @@ -717,7 +717,13 @@ export const TableReview: FC = (props: TableReviewProps) => { className: styles.textBlue, columnId: 'actions', label: 'Actions', - renderer: renderActionsCell, + renderer: (row: SubmissionReviewerRow) => ( + row.isFirstReviewerRow ? renderActionsCell(row) : ( + + -- + + ) + ), type: 'element', }) } @@ -727,16 +733,25 @@ export const TableReview: FC = (props: TableReviewProps) => { columnId: 'ai-reviews-table', isExpand: true, label: '', - renderer: (submission: SubmissionRow, allRows: SubmissionRow[]) => ( - props.aiReviewers && ( + renderer: (row: SubmissionReviewerRow, allRows: SubmissionReviewerRow[]) => { + if (!row.isLastReviewerRow || !props.aiReviewers) { + return + } + + const firstIndexForSubmission = allRows.findIndex(candidate => ( + candidate.id === row.id && candidate.isFirstReviewerRow + )) + const defaultOpen = firstIndexForSubmission === 0 + + return ( ) - ), + }, type: 'element', }) } @@ -745,7 +760,6 @@ export const TableReview: FC = (props: TableReviewProps) => { }, [ downloadButtonConfig, hideHandleColumn, - maxReviewCount, minimumPassingScoreByScorecardId, renderActionsCell, scoreVisibilityConfig, @@ -755,43 +769,13 @@ export const TableReview: FC = (props: TableReviewProps) => { openReopenDialog, challengeInfo, pendingReopen, - reviewerColumnMetadata, ]) - const columnsMobile = useMemo[][]>( + const columnsMobile = useMemo[][]>( () => columns.map(column => { - const resolveLabelString = (): string => { - if (typeof column.label === 'string') { - return column.label - } - - if (typeof column.label === 'function') { - const labelResult = column.label() - if (typeof labelResult === 'string') { - return labelResult - } - - const columnId = column.columnId ?? '' - if (columnId.startsWith('reviewer-')) { - const reviewerIndexRaw = columnId.split('-')[1] - const reviewerIndex = reviewerIndexRaw - ? Number.parseInt(reviewerIndexRaw, 10) - : NaN - if (!Number.isNaN(reviewerIndex)) { - return reviewerColumnMetadata[reviewerIndex]?.label - ?? `Reviewer ${reviewerIndex + 1}` - } - - return 'Reviewer' - } - - return '' - } - - return column.label ?? '' - } - - const resolvedLabel = resolveLabelString() + const resolvedLabel = typeof column.label === 'function' + ? column.label() ?? '' + : (column.label ?? '') const labelForAction = typeof column.label === 'string' ? column.label : resolvedLabel @@ -829,9 +813,9 @@ export const TableReview: FC = (props: TableReviewProps) => { colSpan: labelText ? 1 : 2, mobileType: 'last-value', }, - ].filter(Boolean) as MobileTableColumn[] + ].filter(Boolean) as MobileTableColumn[] }), - [columns, reviewerColumnMetadata], + [columns], ) return ( @@ -843,14 +827,14 @@ export const TableReview: FC = (props: TableReviewProps) => { )} > {isTablet ? ( - + ) : (
{ + const reviews = submission.aggregated?.reviews ?? [] + const reviewCount = reviews.length || 1 + + for (let reviewerIndex = 0; reviewerIndex < reviewCount; reviewerIndex += 1) { + rows.push({ + ...submission, + reviewerIndex, + isFirstReviewerRow: reviewerIndex === 0, + isLastReviewerRow: reviewerIndex === reviewCount - 1, + }) + } + }) + + return rows +} + /** * Shared configuration available to column renderers that need challenge-level context. */ From 632fc1eeb30e9b7c87a75513bc283ca64d4583d6 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 4 Mar 2026 20:40:28 +0100 Subject: [PATCH 02/18] PM-4079 #time 2h fixed border issues in submission table --- .../components/TableAppeals/TableAppeals.tsx | 4 ++- .../TableAppealsResponse.tsx | 4 ++- .../components/TableReview/TableReview.tsx | 25 +++++++++++-------- src/apps/review/src/lib/styles/index.scss | 16 +++++++++++- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx b/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx index 3dd8ba068..fc5320837 100644 --- a/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx +++ b/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx @@ -300,7 +300,7 @@ export const TableAppeals: FC = (props: TableAppealsProps) => const columns = useMemo[]>(() => { const submissionIdColumn: TableColumn = { - className: styles.submissionColumn, + className: classNames(styles.submissionColumn, 'no-row-border'), columnId: 'submission-id', label: 'Submission ID', propertyName: 'id', @@ -316,6 +316,7 @@ export const TableAppeals: FC = (props: TableAppealsProps) => if (!hideHandleColumn) { baseColumns.push({ + className: 'no-row-border', columnId: 'handle-aggregated', label: 'Submitter', propertyName: 'handle', @@ -330,6 +331,7 @@ export const TableAppeals: FC = (props: TableAppealsProps) => if (shouldShowAggregatedReviewScore) { baseColumns.push({ + className: 'no-row-border', columnId: 'review-score', label: 'Review Score', renderer: (row: SubmissionReviewerRow) => ( diff --git a/src/apps/review/src/lib/components/TableAppealsResponse/TableAppealsResponse.tsx b/src/apps/review/src/lib/components/TableAppealsResponse/TableAppealsResponse.tsx index 083a7cc17..115289f0c 100644 --- a/src/apps/review/src/lib/components/TableAppealsResponse/TableAppealsResponse.tsx +++ b/src/apps/review/src/lib/components/TableAppealsResponse/TableAppealsResponse.tsx @@ -324,7 +324,7 @@ export const TableAppealsResponse: FC = (props: Table const columns = useMemo[]>(() => { const submissionIdColumn: TableColumn = { - className: styles.submissionColumn, + className: classNames(styles.submissionColumn, 'no-row-border'), columnId: 'submission-id', label: 'Submission ID', propertyName: 'id', @@ -340,6 +340,7 @@ export const TableAppealsResponse: FC = (props: Table if (!hideHandleColumn) { baseColumns.push({ + className: 'no-row-border', columnId: 'handle-aggregated', label: 'Submitter', propertyName: 'handle', @@ -353,6 +354,7 @@ export const TableAppealsResponse: FC = (props: Table } baseColumns.push({ + className: 'no-row-border', columnId: 'review-score', label: 'Review Score', renderer: (row: SubmissionReviewerRow) => ( diff --git a/src/apps/review/src/lib/components/TableReview/TableReview.tsx b/src/apps/review/src/lib/components/TableReview/TableReview.tsx index 37073d3ea..185ec31d5 100644 --- a/src/apps/review/src/lib/components/TableReview/TableReview.tsx +++ b/src/apps/review/src/lib/components/TableReview/TableReview.tsx @@ -610,7 +610,7 @@ export const TableReview: FC = (props: TableReviewProps) => { const columns = useMemo[]>(() => { const submissionIdColumn: TableColumn = { - className: styles.submissionColumn, + className: classNames(styles.submissionColumn, 'no-row-border'), columnId: 'submission-id', label: 'Submission ID', propertyName: 'id', @@ -626,6 +626,7 @@ export const TableReview: FC = (props: TableReviewProps) => { if (!hideHandleColumn) { baseColumns.push({ + className: 'no-row-border', columnId: 'handle-aggregated', label: 'Submitter', propertyName: 'handle', @@ -638,6 +639,18 @@ export const TableReview: FC = (props: TableReviewProps) => { }) } + baseColumns.push({ + className: 'no-row-border', + columnId: 'review-score', + label: 'Review Score', + renderer: (row: SubmissionReviewerRow) => ( + row.isFirstReviewerRow + ? renderReviewScoreCell(row, scoreVisibilityConfig) + : + ), + type: 'element', + }) + baseColumns.push( { columnId: 'reviewer', @@ -654,16 +667,6 @@ export const TableReview: FC = (props: TableReviewProps) => { renderer: (row: SubmissionReviewerRow) => renderReviewDateCell(row), type: 'element', }, - { - columnId: 'review-score', - label: 'Review Score', - renderer: (row: SubmissionReviewerRow) => ( - row.isFirstReviewerRow - ? renderReviewScoreCell(row, scoreVisibilityConfig) - : - ), - type: 'element', - }, { columnId: 'score', label: 'Score', diff --git a/src/apps/review/src/lib/styles/index.scss b/src/apps/review/src/lib/styles/index.scss index b284ffaea..4d5786723 100644 --- a/src/apps/review/src/lib/styles/index.scss +++ b/src/apps/review/src/lib/styles/index.scss @@ -53,6 +53,7 @@ $icons: review appeal submission warning error event timer upload reopen 1st 2nd } table { width: 100%; + border-collapse: collapse; thead { tr { th { @@ -73,7 +74,6 @@ $icons: review appeal submission warning error event timer upload reopen 1st 2nd } } td { - border-bottom: var(--TableBorderColor) solid 1px; color: var(--FontColor); font-family: "Nunito Sans", sans-serif; font-size: 14px; @@ -93,6 +93,20 @@ $icons: review appeal submission warning error event timer upload reopen 1st 2nd text-transform: none; } } + &:not(.no-row-border) { + border-bottom: var(--TableBorderColor) solid 1px; + } + &.no-row-border { + border-bottom: none; + border-top: none; + } + } + tbody tr:has(td[colspan] .TableCell_blockExpandValue .TableCell_blockCell span:only-child:empty) td { + border-bottom: none; + border-top: none; + padding-top: 0; + padding-bottom: 0; + line-height: 0; } } } From 325caffc448938a6076650251708152767ed623c Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 4 Mar 2026 20:41:28 +0100 Subject: [PATCH 03/18] fix: lint --- .../review/src/lib/components/TableAppeals/TableAppeals.tsx | 2 +- .../components/TableAppealsResponse/TableAppealsResponse.tsx | 2 +- src/apps/review/src/lib/components/TableReview/TableReview.tsx | 2 +- src/apps/review/src/lib/components/common/types.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx b/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx index fc5320837..64241782b 100644 --- a/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx +++ b/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx @@ -42,8 +42,8 @@ import { import type { DownloadButtonConfig, ScoreVisibilityConfig, - SubmissionRow, SubmissionReviewerRow, + SubmissionRow, } from '../common/types' import { buildSubmissionReviewerRows } from '../common/types' import type { AggregatedSubmissionReviews } from '../../utils/aggregateSubmissionReviews' diff --git a/src/apps/review/src/lib/components/TableAppealsResponse/TableAppealsResponse.tsx b/src/apps/review/src/lib/components/TableAppealsResponse/TableAppealsResponse.tsx index 115289f0c..7ce7dde04 100644 --- a/src/apps/review/src/lib/components/TableAppealsResponse/TableAppealsResponse.tsx +++ b/src/apps/review/src/lib/components/TableAppealsResponse/TableAppealsResponse.tsx @@ -44,8 +44,8 @@ import { import type { DownloadButtonConfig, ScoreVisibilityConfig, - SubmissionRow, SubmissionReviewerRow, + SubmissionRow, } from '../common/types' import { buildSubmissionReviewerRows } from '../common/types' import type { AggregatedSubmissionReviews } from '../../utils/aggregateSubmissionReviews' diff --git a/src/apps/review/src/lib/components/TableReview/TableReview.tsx b/src/apps/review/src/lib/components/TableReview/TableReview.tsx index 185ec31d5..814af5528 100644 --- a/src/apps/review/src/lib/components/TableReview/TableReview.tsx +++ b/src/apps/review/src/lib/components/TableReview/TableReview.tsx @@ -61,8 +61,8 @@ import { import type { DownloadButtonConfig, ScoreVisibilityConfig, - SubmissionRow, SubmissionReviewerRow, + SubmissionRow, } from '../common/types' import { buildSubmissionReviewerRows } from '../common/types' import { resolveSubmissionReviewResult } from '../common/reviewResult' diff --git a/src/apps/review/src/lib/components/common/types.ts b/src/apps/review/src/lib/components/common/types.ts index dfe6b5485..510bc7d6b 100644 --- a/src/apps/review/src/lib/components/common/types.ts +++ b/src/apps/review/src/lib/components/common/types.ts @@ -47,9 +47,9 @@ export function buildSubmissionReviewerRows( for (let reviewerIndex = 0; reviewerIndex < reviewCount; reviewerIndex += 1) { rows.push({ ...submission, - reviewerIndex, isFirstReviewerRow: reviewerIndex === 0, isLastReviewerRow: reviewerIndex === reviewCount - 1, + reviewerIndex, }) } }) From 4ae3df34379dbfd4cdd56fda715c32a7fa5b08b5 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 4 Mar 2026 22:11:06 +0100 Subject: [PATCH 04/18] PM-4079 #time 30m fixed error in mobile resolution --- .../src/lib/components/common/TableMobile/TableMobile.tsx | 1 + .../src/lib/components/TableAppeals/TableAppeals.module.scss | 1 - .../review/src/lib/components/TableReview/TableReview.tsx | 5 +++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/apps/admin/src/lib/components/common/TableMobile/TableMobile.tsx b/src/apps/admin/src/lib/components/common/TableMobile/TableMobile.tsx index 79bcb4295..ee63a4a90 100644 --- a/src/apps/admin/src/lib/components/common/TableMobile/TableMobile.tsx +++ b/src/apps/admin/src/lib/components/common/TableMobile/TableMobile.tsx @@ -44,6 +44,7 @@ export const TableMobile: ( {...itemItemColumns} data={itemData} index={indexData} + allRows={props.data} key={getKey([ indexData, indexColumns, diff --git a/src/apps/review/src/lib/components/TableAppeals/TableAppeals.module.scss b/src/apps/review/src/lib/components/TableAppeals/TableAppeals.module.scss index 7ea3ce4a5..c65ae7124 100644 --- a/src/apps/review/src/lib/components/TableAppeals/TableAppeals.module.scss +++ b/src/apps/review/src/lib/components/TableAppeals/TableAppeals.module.scss @@ -55,7 +55,6 @@ white-space: nowrap; } - .mobileLabel { display: inline-block; font-weight: 600; diff --git a/src/apps/review/src/lib/components/TableReview/TableReview.tsx b/src/apps/review/src/lib/components/TableReview/TableReview.tsx index 814af5528..db3c1a975 100644 --- a/src/apps/review/src/lib/components/TableReview/TableReview.tsx +++ b/src/apps/review/src/lib/components/TableReview/TableReview.tsx @@ -736,12 +736,13 @@ export const TableReview: FC = (props: TableReviewProps) => { columnId: 'ai-reviews-table', isExpand: true, label: '', - renderer: (row: SubmissionReviewerRow, allRows: SubmissionReviewerRow[]) => { + renderer: (row: SubmissionReviewerRow, allRows?: SubmissionReviewerRow[]) => { if (!row.isLastReviewerRow || !props.aiReviewers) { return } - const firstIndexForSubmission = allRows.findIndex(candidate => ( + const rows = allRows ?? [] + const firstIndexForSubmission = rows.findIndex(candidate => ( candidate.id === row.id && candidate.isFirstReviewerRow )) const defaultOpen = firstIndexForSubmission === 0 From 5dfafd61008801020e1c441a25c0e94c3799b058 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 4 Mar 2026 22:23:55 +0100 Subject: [PATCH 05/18] fix: lint --- .../components/TableAppeals/TableAppeals.tsx | 2 +- .../TableAppealsResponse.tsx | 2 +- .../components/TableReview/TableReview.tsx | 3 +-- .../src/lib/components/common/reviewResult.ts | 24 ++++++++++++++++- .../review/src/lib/components/common/types.ts | 27 ------------------- 5 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx b/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx index 64241782b..d854abf1a 100644 --- a/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx +++ b/src/apps/review/src/lib/components/TableAppeals/TableAppeals.tsx @@ -45,9 +45,9 @@ import type { SubmissionReviewerRow, SubmissionRow, } from '../common/types' -import { buildSubmissionReviewerRows } from '../common/types' import type { AggregatedSubmissionReviews } from '../../utils/aggregateSubmissionReviews' import { CollapsibleAiReviewsRow } from '../CollapsibleAiReviewsRow' +import { buildSubmissionReviewerRows } from '../common/reviewResult' import styles from './TableAppeals.module.scss' diff --git a/src/apps/review/src/lib/components/TableAppealsResponse/TableAppealsResponse.tsx b/src/apps/review/src/lib/components/TableAppealsResponse/TableAppealsResponse.tsx index 7ce7dde04..20e7941be 100644 --- a/src/apps/review/src/lib/components/TableAppealsResponse/TableAppealsResponse.tsx +++ b/src/apps/review/src/lib/components/TableAppealsResponse/TableAppealsResponse.tsx @@ -47,10 +47,10 @@ import type { SubmissionReviewerRow, SubmissionRow, } from '../common/types' -import { buildSubmissionReviewerRows } from '../common/types' import type { AggregatedSubmissionReviews } from '../../utils/aggregateSubmissionReviews' import type { UseSubmissionDownloadAccessResult } from '../../hooks/useSubmissionDownloadAccess' import { CollapsibleAiReviewsRow } from '../CollapsibleAiReviewsRow' +import { buildSubmissionReviewerRows } from '../common/reviewResult' import styles from './TableAppealsResponse.module.scss' diff --git a/src/apps/review/src/lib/components/TableReview/TableReview.tsx b/src/apps/review/src/lib/components/TableReview/TableReview.tsx index db3c1a975..ba30d6547 100644 --- a/src/apps/review/src/lib/components/TableReview/TableReview.tsx +++ b/src/apps/review/src/lib/components/TableReview/TableReview.tsx @@ -64,8 +64,7 @@ import type { SubmissionReviewerRow, SubmissionRow, } from '../common/types' -import { buildSubmissionReviewerRows } from '../common/types' -import { resolveSubmissionReviewResult } from '../common/reviewResult' +import { buildSubmissionReviewerRows, resolveSubmissionReviewResult } from '../common/reviewResult' import { shouldIncludeInReviewPhase } from '../../utils/reviewPhaseGuards' import { CollapsibleAiReviewsRow } from '../CollapsibleAiReviewsRow' diff --git a/src/apps/review/src/lib/components/common/reviewResult.ts b/src/apps/review/src/lib/components/common/reviewResult.ts index 64a8484e7..b39cde595 100644 --- a/src/apps/review/src/lib/components/common/reviewResult.ts +++ b/src/apps/review/src/lib/components/common/reviewResult.ts @@ -1,4 +1,4 @@ -import type { AggregatedReviewDetail, SubmissionRow } from './types' +import type { AggregatedReviewDetail, SubmissionReviewerRow, SubmissionRow } from './types' export type ReviewOutcome = 'PASS' | 'FAIL' @@ -480,3 +480,25 @@ export function resolveSubmissionReviewResult( return scoreOutcome ?? metadataOutcome } + +export function buildSubmissionReviewerRows( + submissions: SubmissionRow[], +): SubmissionReviewerRow[] { + const rows: SubmissionReviewerRow[] = [] + + submissions.forEach(submission => { + const reviews = submission.aggregated?.reviews ?? [] + const reviewCount = reviews.length || 1 + + for (let reviewerIndex = 0; reviewerIndex < reviewCount; reviewerIndex += 1) { + rows.push({ + ...submission, + isFirstReviewerRow: reviewerIndex === 0, + isLastReviewerRow: reviewerIndex === reviewCount - 1, + reviewerIndex, + }) + } + }) + + return rows +} diff --git a/src/apps/review/src/lib/components/common/types.ts b/src/apps/review/src/lib/components/common/types.ts index 510bc7d6b..1fd272157 100644 --- a/src/apps/review/src/lib/components/common/types.ts +++ b/src/apps/review/src/lib/components/common/types.ts @@ -30,33 +30,6 @@ export interface SubmissionReviewerRow extends SubmissionRow { /** True when this is the last reviewer row for the submission. */ isLastReviewerRow: boolean } - -/** - * Build a flattened list of per-reviewer rows from aggregated submission rows. - * Each SubmissionRow is expanded to one or more SubmissionReviewerRow entries. - */ -export function buildSubmissionReviewerRows( - submissions: SubmissionRow[], -): SubmissionReviewerRow[] { - const rows: SubmissionReviewerRow[] = [] - - submissions.forEach(submission => { - const reviews = submission.aggregated?.reviews ?? [] - const reviewCount = reviews.length || 1 - - for (let reviewerIndex = 0; reviewerIndex < reviewCount; reviewerIndex += 1) { - rows.push({ - ...submission, - isFirstReviewerRow: reviewerIndex === 0, - isLastReviewerRow: reviewerIndex === reviewCount - 1, - reviewerIndex, - }) - } - }) - - return rows -} - /** * Shared configuration available to column renderers that need challenge-level context. */ From 73f8580ff691536d84a22de7a84866b46b78f184 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 5 Mar 2026 12:09:32 +0200 Subject: [PATCH 06/18] PM-4182 #time 1h tweaks for open to work status --- .../src/pages/open-to-work/index.tsx | 2 +- .../OpenForGigs/OpenForGigs.tsx | 17 ++++++-- .../OpenForGigsModifyModal.tsx | 10 ++--- .../profile-header/ProfileHeader.tsx | 8 +++- .../lib/profile/modify-user-profile.model.ts | 2 +- .../ModifyOpenToWorkModal.module.scss | 13 ++++++ .../ModifyOpenToWorkModal.tsx | 42 ++++++++++++------- 7 files changed, 67 insertions(+), 27 deletions(-) diff --git a/src/apps/onboarding/src/pages/open-to-work/index.tsx b/src/apps/onboarding/src/pages/open-to-work/index.tsx index e528028dd..224721b30 100644 --- a/src/apps/onboarding/src/pages/open-to-work/index.tsx +++ b/src/apps/onboarding/src/pages/open-to-work/index.tsx @@ -116,7 +116,7 @@ export const PageOpenToWorkContent: FC = props => { try { const [, updatedTraits] = await Promise.all([ // profile flag - props.updateMemberOpenForWork(formValue.availableForGigs), + props.updateMemberOpenForWork(!!formValue.availableForGigs), // personalization trait updateOrCreateMemberTraitsAsync(props.profileHandle, [{ diff --git a/src/apps/profiles/src/member-profile/profile-header/OpenForGigs/OpenForGigs.tsx b/src/apps/profiles/src/member-profile/profile-header/OpenForGigs/OpenForGigs.tsx index be8e00dc5..0f1cf0b41 100644 --- a/src/apps/profiles/src/member-profile/profile-header/OpenForGigs/OpenForGigs.tsx +++ b/src/apps/profiles/src/member-profile/profile-header/OpenForGigs/OpenForGigs.tsx @@ -12,6 +12,7 @@ import styles from './OpenForGigs.module.scss' interface OpenForGigsProps { canEdit: boolean + isOpenToWork: boolean | null authProfile: UserProfile | undefined profile: UserProfile refreshProfile: (handle: string) => void @@ -27,7 +28,7 @@ const OpenForGigs: FC = (props: OpenForGigsProps) => { const [isEditMode, setIsEditMode]: [boolean, Dispatch>] = useState(false) - const openForWork = props.profile.availableForGigs + const openForWork = props.isOpenToWork useEffect(() => { if (props.authProfile && editMode === profileEditModes.openForWork) { @@ -53,9 +54,16 @@ const OpenForGigs: FC = (props: OpenForGigsProps) => { return props.canEdit || openForWork || props.isPrivilegedViewer ? (
-

- {openForWork ? 'open to work' : 'not open to work'} -

+ {openForWork === null ? ( +

+ Unknown +

+ ): ( +

+ {openForWork ? 'open to work' : 'not open to work'} +

+ )} + { props.canEdit && ( = (props: OpenForGigsProps) => { onClose={handleModifyOpenForWorkClose} onSave={handleModifyOpenForWorkSave} profile={props.profile} + openForWork={openForWork} memberPersonalizationTraits={props.memberPersonalizationTraits} mutatePersonalizationTraits={props.mutatePersonalizationTraits} /> diff --git a/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx b/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx index 3770ca2e8..be65bbbfb 100644 --- a/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx +++ b/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx @@ -22,6 +22,7 @@ interface OpenForGigsModifyModalProps { onClose: () => void onSave: () => void profile: UserProfile + openForWork: boolean | null memberPersonalizationTraits?: UserTrait[] mutatePersonalizationTraits: () => void } @@ -34,7 +35,7 @@ const OpenForGigsModifyModal: FC = (props: OpenForG const [formValue, setFormValue] = useState({ availability: undefined, - availableForGigs: !!props.profile.availableForGigs, + availableForGigs: props.openForWork, preferredRoles: [], }) @@ -56,12 +57,12 @@ const OpenForGigsModifyModal: FC = (props: OpenForG setFormValue(prev => ({ ...prev, availability: openToWorkItem?.availability, - availableForGigs: !!props.profile.availableForGigs, + availableForGigs: props.openForWork, preferredRoles: openToWorkItem?.preferredRoles ?? [], })) }, [ memberPersonalizationTraits, - props.profile.availableForGigs, + props.openForWork, ]) function handleFormChange(nextValue: OpenToWorkData): void { @@ -152,8 +153,7 @@ const OpenForGigsModifyModal: FC = (props: OpenForG >

- By selecting “Open to Work” our talent management team will know - that you are available for engagement opportunities. + By selecting “Open to Work” our customers will know that you are available for engagement opportunities.

diff --git a/src/apps/profiles/src/member-profile/profile-header/ProfileHeader.tsx b/src/apps/profiles/src/member-profile/profile-header/ProfileHeader.tsx index 2c1bc1cb0..3ef72293a 100644 --- a/src/apps/profiles/src/member-profile/profile-header/ProfileHeader.tsx +++ b/src/apps/profiles/src/member-profile/profile-header/ProfileHeader.tsx @@ -7,6 +7,7 @@ import moment from 'moment' import { NamesAndHandleAppearance, useMemberTraits, + useProfileCompleteness, UserProfile, UserRole, UserTrait, @@ -70,7 +71,7 @@ const ProfileHeader: FC = (props: ProfileHeaderProps) => { [state?.queriedSkills], ) - const activeTooltipText = canEdit ? `You have been active in the past 3 months. + const activeTooltipText = canEdit ? `You have been active in the past 3 months. (this information is visible to you only)` : `${props.profile.firstName} has been active in the past 3 months.` useEffect(() => { @@ -146,13 +147,15 @@ const ProfileHeader: FC = (props: ProfileHeaderProps) => { (item: UserTrait) => !!item?.openToWork, ) + const isOpenToWork = hasOpenToWork ? props.profile.availableForGigs : null + function renderOpenForWork(): JSX.Element { const showMyStatusLabel = canEdit const showAdminLabel = isPrivilegedViewer const content = (
- {showMyStatusLabel && My status:} + {showMyStatusLabel && Engagement status:} {showAdminLabel && ( @@ -169,6 +172,7 @@ const ProfileHeader: FC = (props: ProfileHeaderProps) => { isPrivilegedViewer={isPrivilegedViewer} memberPersonalizationTraits={memberPersonalizationTraits} mutatePersonalizationTraits={mutateTraits} + isOpenToWork={isOpenToWork} />
) diff --git a/src/libs/core/lib/profile/modify-user-profile.model.ts b/src/libs/core/lib/profile/modify-user-profile.model.ts index bd3aef1e5..508fffa65 100644 --- a/src/libs/core/lib/profile/modify-user-profile.model.ts +++ b/src/libs/core/lib/profile/modify-user-profile.model.ts @@ -8,7 +8,7 @@ export interface UpdateProfileRequest { streetAddr2?: string zip?: string }> - availableForGigs?: boolean, + availableForGigs?: boolean | null, competitionCountryCode?: string homeCountryCode?: string email?: string diff --git a/src/libs/shared/lib/components/modify-open-to-work-modal/ModifyOpenToWorkModal.module.scss b/src/libs/shared/lib/components/modify-open-to-work-modal/ModifyOpenToWorkModal.module.scss index 1fc86080e..f49ecfd84 100644 --- a/src/libs/shared/lib/components/modify-open-to-work-modal/ModifyOpenToWorkModal.module.scss +++ b/src/libs/shared/lib/components/modify-open-to-work-modal/ModifyOpenToWorkModal.module.scss @@ -22,3 +22,16 @@ } } } + +.radioGroup { + display: flex; + margin-bottom: $sp-6; + > * { + flex: 1; + } + + @include ltemd { + flex-direction: column; + gap: $sp-4; + } +} diff --git a/src/libs/shared/lib/components/modify-open-to-work-modal/ModifyOpenToWorkModal.tsx b/src/libs/shared/lib/components/modify-open-to-work-modal/ModifyOpenToWorkModal.tsx index 3c07480a0..cf109f6fa 100644 --- a/src/libs/shared/lib/components/modify-open-to-work-modal/ModifyOpenToWorkModal.tsx +++ b/src/libs/shared/lib/components/modify-open-to-work-modal/ModifyOpenToWorkModal.tsx @@ -1,13 +1,13 @@ import { ChangeEvent, FC, useCallback } from 'react' -import { InputMultiselect, InputMultiselectOption, InputSelect, InputText } from '~/libs/ui' +import { InputMultiselect, InputMultiselectOption, InputRadio, InputSelect } from '~/libs/ui' import styles from './ModifyOpenToWorkModal.module.scss' export type AvailabilityType = 'FULL_TIME' | 'PART_TIME' export interface OpenToWorkData { - availableForGigs: boolean + availableForGigs: boolean | null availability?: AvailabilityType preferredRoles?: string[] } @@ -57,10 +57,12 @@ export const validateOpenToWork = (value: OpenToWorkData): { [key: string]: stri } const OpenToWorkForm: FC = (props: OpenToWorkFormProps) => { - function toggleOpenForWork(): void { + function handleOpenForWorkChange(e: ChangeEvent): void { + const openForWork = e.target.value === 'true' + props.onChange({ ...props.value, - availableForGigs: !props.value.availableForGigs, + availableForGigs: openForWork, }) } @@ -96,20 +98,32 @@ const OpenToWorkForm: FC = (props: OpenToWorkFormProps) => return (
- +
+ + +
{props.value.availableForGigs && ( <> = (props: OpenToWorkFormProps) => Date: Thu, 5 Mar 2026 12:42:02 +0200 Subject: [PATCH 07/18] lint fix --- .../member-profile/profile-header/OpenForGigs/OpenForGigs.tsx | 2 +- .../OpenForGigsModifyModal/OpenForGigsModifyModal.tsx | 3 ++- .../src/member-profile/profile-header/ProfileHeader.tsx | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/apps/profiles/src/member-profile/profile-header/OpenForGigs/OpenForGigs.tsx b/src/apps/profiles/src/member-profile/profile-header/OpenForGigs/OpenForGigs.tsx index 0f1cf0b41..7e7db2ecc 100644 --- a/src/apps/profiles/src/member-profile/profile-header/OpenForGigs/OpenForGigs.tsx +++ b/src/apps/profiles/src/member-profile/profile-header/OpenForGigs/OpenForGigs.tsx @@ -58,7 +58,7 @@ const OpenForGigs: FC = (props: OpenForGigsProps) => {

Unknown

- ): ( + ) : (

{openForWork ? 'open to work' : 'not open to work'}

diff --git a/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx b/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx index be65bbbfb..5d56b5030 100644 --- a/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx +++ b/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx @@ -153,7 +153,8 @@ const OpenForGigsModifyModal: FC = (props: OpenForG >

- By selecting “Open to Work” our customers will know that you are available for engagement opportunities. + By selecting “Open to Work” our customers will know that + you are available for engagement opportunities.

diff --git a/src/apps/profiles/src/member-profile/profile-header/ProfileHeader.tsx b/src/apps/profiles/src/member-profile/profile-header/ProfileHeader.tsx index 3ef72293a..62b721b65 100644 --- a/src/apps/profiles/src/member-profile/profile-header/ProfileHeader.tsx +++ b/src/apps/profiles/src/member-profile/profile-header/ProfileHeader.tsx @@ -1,4 +1,5 @@ /* eslint-disable complexity */ +/* eslint-disable unicorn/no-null */ import { Dispatch, FC, SetStateAction, useEffect, useMemo, useState } from 'react' import { Location, useLocation, useSearchParams } from 'react-router-dom' import { KeyedMutator } from 'swr' @@ -7,7 +8,6 @@ import moment from 'moment' import { NamesAndHandleAppearance, useMemberTraits, - useProfileCompleteness, UserProfile, UserRole, UserTrait, From dfdcbe0b8985e71d37a60571a416242da5d3cfc4 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 5 Mar 2026 13:57:23 +0200 Subject: [PATCH 08/18] PM-4198 #time 2h add pagination & member details to custmer portal results --- .../lib/services/profileCompletion.service.ts | 118 ++++++++ .../ProfileCompletionPage.module.scss | 59 +++- .../ProfileCompletionPage.tsx | 279 ++++++++++++------ 3 files changed, 358 insertions(+), 98 deletions(-) create mode 100644 src/apps/customer-portal/src/lib/services/profileCompletion.service.ts diff --git a/src/apps/customer-portal/src/lib/services/profileCompletion.service.ts b/src/apps/customer-portal/src/lib/services/profileCompletion.service.ts new file mode 100644 index 000000000..0db964f8e --- /dev/null +++ b/src/apps/customer-portal/src/lib/services/profileCompletion.service.ts @@ -0,0 +1,118 @@ +import { EnvironmentConfig } from '~/config' +import { UserSkill, xhrGetAsync } from '~/libs/core' + +export type CompletedProfile = { + countryCode?: string + countryName?: string + city?: string + firstName?: string + handle: string + lastName?: string + photoURL?: string + skillCount?: number + userId?: number | string +} + +export type CompletedProfilesResponse = { + data: CompletedProfile[] + page: number + perPage: number + total: number + totalPages: number +} + +export const DEFAULT_PAGE_SIZE = 50 + +function normalizeToList(raw: any): any[] { + if (Array.isArray(raw)) { + return raw + } + + if (Array.isArray(raw?.data)) { + return raw.data + } + + if (Array.isArray(raw?.result?.content)) { + return raw.result.content + } + + if (Array.isArray(raw?.result)) { + return raw.result + } + + return [] +} + +function normalizeCompletedProfilesResponse( + raw: any, + fallbackPage: number, + fallbackPerPage: number, +): CompletedProfilesResponse { + if (raw && Array.isArray(raw.data)) { + const total: number = Number(raw.total ?? raw.data.length) + const perPage: number = Number(raw.perPage ?? fallbackPerPage) + const page: number = Number(raw.page ?? fallbackPage) + const safePerPage = Number.isFinite(perPage) ? Math.max(perPage, 1) : fallbackPerPage + const safeTotal = Number.isFinite(total) ? Math.max(total, 0) : raw.data.length + + return { + data: raw.data, + page: Number.isFinite(page) ? Math.max(page, 1) : fallbackPage, + perPage: safePerPage, + total: safeTotal, + totalPages: Number.isFinite(raw.totalPages) + ? Math.max(Number(raw.totalPages), 1) + : Math.max(Math.ceil(safeTotal / safePerPage), 1), + } + } + + const rows = normalizeToList(raw) + const total = Number(raw?.total ?? rows.length) + const safeTotal = Number.isFinite(total) ? Math.max(total, 0) : rows.length + + return { + data: rows, + page: fallbackPage, + perPage: fallbackPerPage, + total: safeTotal, + totalPages: Math.max(Math.ceil(safeTotal / fallbackPerPage), 1), + } +} + +export async function fetchCompletedProfiles( + countryCode: string | undefined, + page: number, + perPage: number, +): Promise { + const queryParams = new URLSearchParams({ + page: String(page), + perPage: String(perPage), + }) + + if (countryCode) { + queryParams.set('countryCode', countryCode) + } + + const response = await xhrGetAsync( + `${EnvironmentConfig.REPORTS_API}/topcoder/completed-profiles?${queryParams.toString()}`, + ) + + return normalizeCompletedProfilesResponse(response, page, perPage) +} + +export async function fetchMemberSkillsData(userId: string | number | undefined): Promise { + if (!userId) { + return [] + } + + const baseUrl = `${EnvironmentConfig.API.V5}/standardized-skills` + const url = `${baseUrl}/user-skills/${userId}?disablePagination=true` + + try { + return await xhrGetAsync(url) + } catch { + // If skills API fails, return empty array to not block the page + return [] + } +} + diff --git a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss index 0a68a0b65..2aae1d425 100644 --- a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss +++ b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss @@ -86,7 +86,7 @@ table { width: 100%; border-collapse: collapse; - min-width: 420px; + min-width: 1120px; } th, @@ -106,9 +106,66 @@ td { color: $black-100; + vertical-align: middle; } tr:last-child td { border-bottom: 0; } } + +.memberCell { + display: flex; + align-items: center; + gap: $sp-2; +} + +.avatar { + width: 28px; + height: 28px; + border-radius: 50%; + object-fit: cover; + border: 1px solid $black-20; +} + +.paginationRow { + display: flex; + align-items: center; + justify-content: space-between; + gap: $sp-3; + + @include ltemd { + flex-direction: column; + align-items: flex-start; + } +} + +.paginationInfo { + color: $black-60; + font-size: 14px; + line-height: 20px; +} + +.paginationButtons { + display: flex; + align-items: center; + gap: $sp-2; +} + +.skillsList { + display: flex; + flex-wrap: wrap; + gap: $sp-2; +} + +.skillTag { + display: inline-block; + background: $black-5; + border: 1px solid $black-20; + border-radius: $sp-1; + padding: $sp-1 $sp-2; + font-size: 12px; + line-height: 16px; + color: $black-80; + white-space: nowrap; +} diff --git a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx index e565731a6..72a1893db 100644 --- a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx +++ b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx @@ -1,65 +1,70 @@ /* eslint-disable react/jsx-no-bind */ -import { ChangeEvent, FC, useMemo, useState } from 'react' +import { ChangeEvent, FC, useEffect, useMemo, useState } from 'react' import useSWR, { SWRResponse } from 'swr' import { EnvironmentConfig } from '~/config' -import { CountryLookup, useCountryLookup, xhrGetAsync } from '~/libs/core' -import { InputSelect, InputSelectOption, LoadingSpinner } from '~/libs/ui' +import { CountryLookup, useCountryLookup, UserSkill, UserSkillDisplayModes } from '~/libs/core' +import { Button, InputSelect, InputSelectOption, LoadingSpinner } from '~/libs/ui' import { PageWrapper } from '../../../lib' +import { + CompletedProfilesResponse, + DEFAULT_PAGE_SIZE, + fetchCompletedProfiles, + fetchMemberSkillsData, +} from '../../../lib/services/profileCompletion.service' import styles from './ProfileCompletionPage.module.scss' -type CompletedProfile = { - countryCode?: string - countryName?: string - handle: string - userId?: number | string -} - -function normalizeToList(raw: any): any[] { - if (Array.isArray(raw)) { - return raw - } - - if (Array.isArray(raw?.data)) { - return raw.data - } - - if (Array.isArray(raw?.result?.content)) { - return raw.result.content - } - - if (Array.isArray(raw?.result)) { - return raw.result - } - - return [] -} - -async function fetchCompletedProfiles(): Promise { - const response = await xhrGetAsync( - `${EnvironmentConfig.REPORTS_API}/topcoder/completed-profiles`, - ) - - return normalizeToList(response) -} - export const ProfileCompletionPage: FC = () => { const [selectedCountry, setSelectedCountry] = useState('all') + const [currentPage, setCurrentPage] = useState(1) + const [memberSkills, setMemberSkills] = useState>(new Map()) const countryLookup: CountryLookup[] | undefined = useCountryLookup() - const { data, error, isValidating }: SWRResponse = useSWR( - 'customer-portal-completed-profiles', - fetchCompletedProfiles, + const countryCodeFilter = selectedCountry === 'all' ? undefined : selectedCountry + + const { data, error, isValidating }: SWRResponse = useSWR( + `customer-portal-completed-profiles:${countryCodeFilter || 'all'}:${currentPage}:${DEFAULT_PAGE_SIZE}`, + () => fetchCompletedProfiles(countryCodeFilter, currentPage, DEFAULT_PAGE_SIZE), { revalidateOnFocus: false, }, ) + // Fetch member skills for all profiles on the current page + useEffect(() => { + if (!data?.data || data.data.length === 0) return + + const fetchAllMemberSkills = async () => { + const skillsMap = new Map() + + for (const profile of data.data) { + if (profile.userId && !memberSkills.has(profile.userId)) { + const skills = await fetchMemberSkillsData(profile.userId) + skillsMap.set(profile.userId, skills) + } + } + + if (skillsMap.size > 0) { + setMemberSkills(prevSkills => { + const newMap = new Map(prevSkills) + skillsMap.forEach((skills, userId) => { + newMap.set(userId, skills) + }) + return newMap + }) + } + } + + fetchAllMemberSkills() + }, [data?.data]) + const countryMap = useMemo(() => { - const map = new Map(); - (countryLookup || []).forEach(country => { + const map = new Map() + const countries = countryLookup || [] + + countries.forEach((country: CountryLookup) => { if (country.countryCode) { map.set(country.countryCode, country.country) } @@ -69,47 +74,62 @@ export const ProfileCompletionPage: FC = () => { }, [countryLookup]) const countryOptions = useMemo(() => { - const dynamicCodes = new Set(); - (data || []).forEach(profile => { - if (profile.countryCode) { - dynamicCodes.add(profile.countryCode) - } - }) + const staticOptions = (countryLookup || []) + .filter(country => !!country.countryCode) + .map(country => ({ + label: country.country, + value: country.countryCode, + })) + .sort((a, b) => String(a.label).localeCompare(String(b.label))) - const dynamicOptions = Array.from(dynamicCodes) - .map(code => ({ - label: countryMap.get(code) || code, - value: code, + const seen = new Set(staticOptions.map(option => option.value)) + const dynamicOptions = (data?.data || []) + .filter(profile => !!profile.countryCode && !seen.has(String(profile.countryCode))) + .map(profile => ({ + label: countryMap.get(String(profile.countryCode)) || profile.countryName || String(profile.countryCode), + value: String(profile.countryCode), })) - .sort((a, b) => String(a.label) - .localeCompare(String(b.label))) + .sort((a, b) => String(a.label).localeCompare(String(b.label))) return [ { label: 'All Countries', value: 'all', }, + ...staticOptions, ...dynamicOptions, ] - }, [countryMap, data]) - - const profiles = useMemo(() => { - const source = data || [] - if (selectedCountry === 'all') { - return source - } + }, [countryLookup, countryMap, data?.data]) - return source.filter(profile => profile.countryCode === selectedCountry) - }, [data, selectedCountry]) + const profiles = data?.data || [] + const totalProfiles = data?.total || 0 + const totalPages = data?.totalPages || 1 const displayedRows = useMemo(() => profiles - .map(profile => ({ - ...profile, - countryLabel: profile.countryCode - ? countryMap.get(profile.countryCode) || profile.countryName || profile.countryCode - : profile.countryName || '-', - })) - .sort((a, b) => a.handle.localeCompare(b.handle)), [profiles, countryMap]) + .map(profile => { + const userSkills = profile.userId ? (memberSkills.get(profile.userId) || []) : [] + const principalSkills = userSkills + .filter(skill => skill.displayMode?.name === UserSkillDisplayModes.principal) + .slice(0, 5) + + return { + ...profile, + fullName: [profile.firstName, profile.lastName].filter(Boolean).join(' ').trim(), + countryLabel: profile.countryCode + ? countryMap.get(profile.countryCode) || profile.countryName || profile.countryCode + : profile.countryName || '-', + locationLabel: [profile.city, profile.countryCode + ? countryMap.get(profile.countryCode) || profile.countryName || profile.countryCode + : profile.countryName] + .filter(Boolean) + .join(', '), + principalSkills, + } + }) + .sort((a, b) => a.handle.localeCompare(b.handle)), [profiles, countryMap, memberSkills]) + + const isPreviousDisabled = currentPage <= 1 || isValidating + const isNextDisabled = isValidating || currentPage >= totalPages return ( { value={selectedCountry} onChange={(event: ChangeEvent) => { setSelectedCountry(event.target.value || 'all') + setCurrentPage(1) }} placeholder='Select country' />
Fully Completed Profiles - {profiles.length} + {totalProfiles}
@@ -155,32 +176,96 @@ export const ProfileCompletionPage: FC = () => { )} {!error && displayedRows.length > 0 && ( -
-
- - - - - - - - {displayedRows.map(profile => ( - - - + <> +
+
HandleCountry
- - {profile.handle} - - {profile.countryLabel}
+ + + + + + + + - ))} - -
MemberHandleLocationSkillsPrincipal SkillsGo to profile
- + + + {displayedRows.map(profile => ( + + +
+ {profile.photoURL && ( + {profile.handle} + )} + {profile.fullName || '-'} +
+ + + + {profile.handle} + + + {profile.locationLabel || profile.countryLabel} + {profile.skillCount ?? '-'} + + {profile.principalSkills && profile.principalSkills.length > 0 ? ( +
+ {profile.principalSkills.map(skill => ( + + {skill.name} + + ))} +
+ ) : ( + '-' + )} + + + + Go to profile + + + + ))} + + + +
+ + Page {currentPage} of {totalPages} + +
+ + +
+
+ )} ) From 6b857428c4549d9a7a77b848b45da3c3565d30c9 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 5 Mar 2026 14:14:13 +0200 Subject: [PATCH 09/18] PM-4198 #time 20m lint --- .../lib/services/profileCompletion.service.ts | 1 - .../ProfileCompletionPage.tsx | 28 +++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/apps/customer-portal/src/lib/services/profileCompletion.service.ts b/src/apps/customer-portal/src/lib/services/profileCompletion.service.ts index 0db964f8e..4c1041ac4 100644 --- a/src/apps/customer-portal/src/lib/services/profileCompletion.service.ts +++ b/src/apps/customer-portal/src/lib/services/profileCompletion.service.ts @@ -115,4 +115,3 @@ export async function fetchMemberSkillsData(userId: string | number | undefined) return [] } } - diff --git a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx index 72a1893db..0553167a3 100644 --- a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx +++ b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx @@ -1,4 +1,6 @@ /* eslint-disable react/jsx-no-bind */ +/* eslint-disable no-await-in-loop */ +/* eslint-disable complexity */ import { ChangeEvent, FC, useEffect, useMemo, useState } from 'react' import useSWR, { SWRResponse } from 'swr' @@ -36,7 +38,7 @@ export const ProfileCompletionPage: FC = () => { useEffect(() => { if (!data?.data || data.data.length === 0) return - const fetchAllMemberSkills = async () => { + const fetchAllMemberSkills = async (): Promise => { const skillsMap = new Map() for (const profile of data.data) { @@ -80,16 +82,22 @@ export const ProfileCompletionPage: FC = () => { label: country.country, value: country.countryCode, })) - .sort((a, b) => String(a.label).localeCompare(String(b.label))) + .sort((a, b) => String(a.label) + .localeCompare(String(b.label))) const seen = new Set(staticOptions.map(option => option.value)) const dynamicOptions = (data?.data || []) .filter(profile => !!profile.countryCode && !seen.has(String(profile.countryCode))) .map(profile => ({ - label: countryMap.get(String(profile.countryCode)) || profile.countryName || String(profile.countryCode), + label: ( + countryMap.get(String(profile.countryCode)) + || profile.countryName + || String(profile.countryCode) + ), value: String(profile.countryCode), })) - .sort((a, b) => String(a.label).localeCompare(String(b.label))) + .sort((a, b) => String(a.label) + .localeCompare(String(b.label))) return [ { @@ -114,10 +122,12 @@ export const ProfileCompletionPage: FC = () => { return { ...profile, - fullName: [profile.firstName, profile.lastName].filter(Boolean).join(' ').trim(), countryLabel: profile.countryCode ? countryMap.get(profile.countryCode) || profile.countryName || profile.countryCode : profile.countryName || '-', + fullName: [profile.firstName, profile.lastName].filter(Boolean) + .join(' ') + .trim(), locationLabel: [profile.city, profile.countryCode ? countryMap.get(profile.countryCode) || profile.countryName || profile.countryCode : profile.countryName] @@ -244,7 +254,13 @@ export const ProfileCompletionPage: FC = () => {
- Page {currentPage} of {totalPages} + Page + {' '} + {currentPage} + {' '} + of + {' '} + {totalPages}
} - > -

- You have successfully changed your account role. - Please sign out of your account and login to complete this update. -

- - ) - } - - - ) -} - -export default AccountRole diff --git a/src/apps/accounts/src/settings/tabs/account/account-role/index.ts b/src/apps/accounts/src/settings/tabs/account/account-role/index.ts deleted file mode 100644 index eafa05f37..000000000 --- a/src/apps/accounts/src/settings/tabs/account/account-role/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as AccountRole } from './AccountRole' diff --git a/src/apps/accounts/src/settings/tabs/account/address/MemberAddress.module.scss b/src/apps/accounts/src/settings/tabs/account/address/MemberAddress.module.scss deleted file mode 100644 index 32ea795a5..000000000 --- a/src/apps/accounts/src/settings/tabs/account/address/MemberAddress.module.scss +++ /dev/null @@ -1,27 +0,0 @@ -@import '@libs/ui/styles/includes'; - -.container { - margin: $sp-8 0; - - .content { - display: grid; - grid-template-columns: repeat(2, 1fr); - margin-bottom: 0; - - @include ltelg { - grid-template-columns: 1fr; - } - - >p { - max-width: 380px; - } - - .form { - .formCTAs { - margin-top: $sp-4; - padding-top: $sp-4; - border-top: 2px solid $black-10; - } - } - } -} \ No newline at end of file diff --git a/src/apps/accounts/src/settings/tabs/account/address/MemberAddress.tsx b/src/apps/accounts/src/settings/tabs/account/address/MemberAddress.tsx deleted file mode 100644 index f99f43989..000000000 --- a/src/apps/accounts/src/settings/tabs/account/address/MemberAddress.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react' -import { toast } from 'react-toastify' -import { bind, trim } from 'lodash' -import classNames from 'classnames' - -import { - Button, - Collapsible, InputSelect, InputText, -} from '~/libs/ui' -import { - CountryLookup, - updateMemberProfileAsync, - useCountryLookup, - UserProfile, -} from '~/libs/core' - -import styles from './MemberAddress.module.scss' - -interface MemberAddressProps { - profile: UserProfile -} - -const MemberAddress: FC = (props: MemberAddressProps) => { - const countryLookup: CountryLookup[] | undefined - = useCountryLookup() - - const contries = useMemo(() => (countryLookup || []).map((cl: CountryLookup) => ({ - label: cl.country, - value: cl.countryCode, - })) - .sort((a, b) => a.label.localeCompare(b.label)), [countryLookup]) - - const [formValues, setFormValues]: [any, Dispatch] = useState({ - country: props.profile.homeCountryCode || props.profile.competitionCountryCode, - ...props.profile.addresses ? props.profile.addresses[0] : {}, - }) - - const [formErrors, setFormErrors]: [ - { [key: string]: string }, - Dispatch> - ] - = useState<{ [key: string]: string }>({}) - - const [isSaving, setIsSaving]: [boolean, Dispatch>] - = useState(false) - - const [isFormChanged, setIsFormChanged]: [boolean, Dispatch>] - = useState(false) - - function handleFormValueChange(key: string, event: React.ChangeEvent): void { - const oldFormValues = { ...formValues } - - setFormValues({ - ...oldFormValues, - [key]: event.target.value, - }) - setIsFormChanged(true) - } - - function handleFormAction(): void { - if (!trim(formValues.city)) { - setFormErrors({ city: 'Please select a city' }) - return - } - - if (!formValues.country) { - setFormErrors({ country: 'Please select a country' }) - return - } - - setIsSaving(true) - - updateMemberProfileAsync( - props.profile.handle, - { - addresses: [{ - city: trim(formValues.city), - stateCode: trim(formValues.stateCode), - streetAddr1: trim(formValues.streetAddr1), - streetAddr2: trim(formValues.streetAddr2), - zip: trim(formValues.zip), - }], - competitionCountryCode: formValues.country, - homeCountryCode: formValues.country, - }, - ) - .then(() => { - toast.success('Your account has been updated.', { position: toast.POSITION.BOTTOM_RIGHT }) - setFormErrors({}) - }) - .catch(() => { - toast.error('Something went wrong. Please try again.', { position: toast.POSITION.BOTTOM_RIGHT }) - }) - .finally(() => { - setIsFormChanged(false) - setIsSaving(false) - }) - } - - return ( - Address} - containerClass={styles.container} - contentClass={styles.content} - > -

- By keeping this information up to date we may surprise you with a cool T-shirt. - Sharing your contact details will never result in robocalls about health insurance plans or junk mail. -

- -
-
- - - - - - - -
-
-
-
-
- ) -} - -export default MemberAddress diff --git a/src/apps/accounts/src/settings/tabs/account/address/index.ts b/src/apps/accounts/src/settings/tabs/account/address/index.ts deleted file mode 100644 index 33f8c6ff5..000000000 --- a/src/apps/accounts/src/settings/tabs/account/address/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as MemberAddress } from './MemberAddress' From 19372bae1bb0fc5d5f8280ce7fbd76884cc8beee Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 5 Mar 2026 15:54:07 +0200 Subject: [PATCH 11/18] PM-4198 #time 30m display all skills --- .../ProfileCompletionPage.module.scss | 15 ++++++++++ .../ProfileCompletionPage.tsx | 30 ++++++++++++++----- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss index 2aae1d425..af40c8571 100644 --- a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss +++ b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss @@ -169,3 +169,18 @@ color: $black-80; white-space: nowrap; } + +.moreIndicator { + display: inline-block; + background: $black-5; + border: 1px solid $black-20; + border-radius: $sp-1; + padding: $sp-1 $sp-2; + font-size: 12px; + line-height: 16px; + color: $black-80; + font-weight: 700; + min-width: 24px; + text-align: center; + cursor: help; +} diff --git a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx index 0553167a3..34b95db38 100644 --- a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx +++ b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx @@ -6,7 +6,7 @@ import useSWR, { SWRResponse } from 'swr' import { EnvironmentConfig } from '~/config' import { CountryLookup, useCountryLookup, UserSkill, UserSkillDisplayModes } from '~/libs/core' -import { Button, InputSelect, InputSelectOption, LoadingSpinner } from '~/libs/ui' +import { Button, InputSelect, InputSelectOption, LoadingSpinner, Tooltip } from '~/libs/ui' import { PageWrapper } from '../../../lib' import { @@ -116,9 +116,15 @@ export const ProfileCompletionPage: FC = () => { const displayedRows = useMemo(() => profiles .map(profile => { const userSkills = profile.userId ? (memberSkills.get(profile.userId) || []) : [] - const principalSkills = userSkills - .filter(skill => skill.displayMode?.name === UserSkillDisplayModes.principal) - .slice(0, 5) + + // Prioritize principal skills, then add additional skills + const allSkillsByPriority = [ + ...userSkills.filter(skill => skill.displayMode?.name === UserSkillDisplayModes.principal), + ...userSkills.filter(skill => skill.displayMode?.name !== UserSkillDisplayModes.principal), + ] + + const displayedSkills = allSkillsByPriority.slice(0, 5) + const additionalSkillsCount = Math.max(0, allSkillsByPriority.length - 5) return { ...profile, @@ -133,7 +139,8 @@ export const ProfileCompletionPage: FC = () => { : profile.countryName] .filter(Boolean) .join(', '), - principalSkills, + displayedSkills, + additionalSkillsCount, } }) .sort((a, b) => a.handle.localeCompare(b.handle)), [profiles, countryMap, memberSkills]) @@ -226,13 +233,22 @@ export const ProfileCompletionPage: FC = () => { {profile.locationLabel || profile.countryLabel} {profile.skillCount ?? '-'} - {profile.principalSkills && profile.principalSkills.length > 0 ? ( + {profile.displayedSkills && profile.displayedSkills.length > 0 ? (
- {profile.principalSkills.map(skill => ( + {profile.displayedSkills.map(skill => ( {skill.name} ))} + {profile.additionalSkillsCount > 0 && ( + + + + + + + )}
) : ( '-' From 6a8f4b199c492e2af7da27d53c0bc139b4add25d Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 5 Mar 2026 16:16:12 +0200 Subject: [PATCH 12/18] lint --- .../ProfileCompletionPage.module.scss | 8 ++++++++ .../ProfileCompletionPage.tsx | 15 +++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss index af40c8571..f0a0e396d 100644 --- a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss +++ b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.module.scss @@ -184,3 +184,11 @@ text-align: center; cursor: help; } + +.link { + display: flex; + gap: $sp-1; + text-decoration: underline; + color: $link-blue; + cursor: pointer; +} diff --git a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx index 34b95db38..955209f20 100644 --- a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx +++ b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx @@ -128,9 +128,11 @@ export const ProfileCompletionPage: FC = () => { return { ...profile, + additionalSkillsCount, countryLabel: profile.countryCode ? countryMap.get(profile.countryCode) || profile.countryName || profile.countryCode : profile.countryName || '-', + displayedSkills, fullName: [profile.firstName, profile.lastName].filter(Boolean) .join(' ') .trim(), @@ -139,8 +141,6 @@ export const ProfileCompletionPage: FC = () => { : profile.countryName] .filter(Boolean) .join(', '), - displayedSkills, - additionalSkillsCount, } }) .sort((a, b) => a.handle.localeCompare(b.handle)), [profiles, countryMap, memberSkills]) @@ -203,7 +203,7 @@ export const ProfileCompletionPage: FC = () => { Location Skills Principal Skills - Go to profile + {' '} @@ -242,7 +242,13 @@ export const ProfileCompletionPage: FC = () => { ))} {profile.additionalSkillsCount > 0 && ( + @@ -256,6 +262,7 @@ export const ProfileCompletionPage: FC = () => { Date: Thu, 5 Mar 2026 16:22:11 +0200 Subject: [PATCH 13/18] PM-4198 - more tweaks to completed profiles table report --- .../ProfileCompletionPage.tsx | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx index 955209f20..e2e85ca06 100644 --- a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx +++ b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx @@ -6,7 +6,7 @@ import useSWR, { SWRResponse } from 'swr' import { EnvironmentConfig } from '~/config' import { CountryLookup, useCountryLookup, UserSkill, UserSkillDisplayModes } from '~/libs/core' -import { Button, InputSelect, InputSelectOption, LoadingSpinner, Tooltip } from '~/libs/ui' +import { Button, InputSelect, InputSelectOption, LoadingSpinner } from '~/libs/ui' import { PageWrapper } from '../../../lib' import { @@ -202,7 +202,6 @@ export const ProfileCompletionPage: FC = () => { Handle Location Skills - Principal Skills {' '} @@ -231,7 +230,6 @@ export const ProfileCompletionPage: FC = () => { {profile.locationLabel || profile.countryLabel} - {profile.skillCount ?? '-'} {profile.displayedSkills && profile.displayedSkills.length > 0 ? (
@@ -241,19 +239,9 @@ export const ProfileCompletionPage: FC = () => { ))} {profile.additionalSkillsCount > 0 && ( - - - + - - + + + {profile.additionalSkillsCount} skills + )}
) : ( From 74aeb63ad800b0a7fec85bd8797e07ea63f2254c Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 5 Mar 2026 16:27:54 +0200 Subject: [PATCH 14/18] lint --- .../ProfileCompletionPage/ProfileCompletionPage.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx index e2e85ca06..34ee7785b 100644 --- a/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx +++ b/src/apps/customer-portal/src/pages/profile-completion/ProfileCompletionPage/ProfileCompletionPage.tsx @@ -240,7 +240,10 @@ export const ProfileCompletionPage: FC = () => { ))} {profile.additionalSkillsCount > 0 && ( - + {profile.additionalSkillsCount} skills + + + {profile.additionalSkillsCount} + {' '} + skills )}
From 15e5a71315c79dcf7183f548acf2d369010201ac Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Fri, 6 Mar 2026 15:27:32 +1100 Subject: [PATCH 15/18] Additional engagements fixes --- .../engagements/src/components/status-badge/StatusBadge.tsx | 1 + src/apps/engagements/src/lib/models/Engagement.model.ts | 1 + .../pages/application-form/ApplicationFormPage.module.scss | 1 + .../src/pages/application-form/ApplicationFormPage.tsx | 5 ++++- .../src/pages/application-form/application-form.schema.ts | 4 ++-- 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/apps/engagements/src/components/status-badge/StatusBadge.tsx b/src/apps/engagements/src/components/status-badge/StatusBadge.tsx index 4a815d672..653f6f5e8 100644 --- a/src/apps/engagements/src/components/status-badge/StatusBadge.tsx +++ b/src/apps/engagements/src/components/status-badge/StatusBadge.tsx @@ -15,6 +15,7 @@ const STATUS_LABELS: Record = { [EngagementStatus.OPEN]: 'Open', [EngagementStatus.PENDING_ASSIGNMENT]: 'Pending Assignment', [EngagementStatus.ACTIVE]: 'Active', + [EngagementStatus.ON_HOLD]: 'On Hold', [EngagementStatus.CANCELLED]: 'Cancelled', [EngagementStatus.CLOSED]: 'Closed', } diff --git a/src/apps/engagements/src/lib/models/Engagement.model.ts b/src/apps/engagements/src/lib/models/Engagement.model.ts index 29c93fa44..580aefcd0 100644 --- a/src/apps/engagements/src/lib/models/Engagement.model.ts +++ b/src/apps/engagements/src/lib/models/Engagement.model.ts @@ -2,6 +2,7 @@ export enum EngagementStatus { OPEN = 'open', PENDING_ASSIGNMENT = 'pending_assignment', ACTIVE = 'active', + ON_HOLD = 'on_hold', CANCELLED = 'cancelled', CLOSED = 'closed', } diff --git a/src/apps/engagements/src/pages/application-form/ApplicationFormPage.module.scss b/src/apps/engagements/src/pages/application-form/ApplicationFormPage.module.scss index dc6d1d58c..a893cf794 100644 --- a/src/apps/engagements/src/pages/application-form/ApplicationFormPage.module.scss +++ b/src/apps/engagements/src/pages/application-form/ApplicationFormPage.module.scss @@ -30,6 +30,7 @@ .readOnlyGrid { @include gridColumns(2, 1, 1, 16px); + align-items: start; } .readOnlyField { diff --git a/src/apps/engagements/src/pages/application-form/ApplicationFormPage.tsx b/src/apps/engagements/src/pages/application-form/ApplicationFormPage.tsx index 736eb09bd..4203b229f 100644 --- a/src/apps/engagements/src/pages/application-form/ApplicationFormPage.tsx +++ b/src/apps/engagements/src/pages/application-form/ApplicationFormPage.tsx @@ -424,9 +424,12 @@ const ApplicationFormPage: FC = () => { (event: ChangeEvent): void => { const nextValue = event.target.value field.onChange(nextValue || undefined) + setValue('mobileNumber', nextValue === '' ? undefined : nextValue, { + shouldValidate: true, + }) } ), - [], + [setValue], ) const handleContactFieldChange = useCallback( diff --git a/src/apps/engagements/src/pages/application-form/application-form.schema.ts b/src/apps/engagements/src/pages/application-form/application-form.schema.ts index bfd1811d5..e9c109371 100644 --- a/src/apps/engagements/src/pages/application-form/application-form.schema.ts +++ b/src/apps/engagements/src/pages/application-form/application-form.schema.ts @@ -32,7 +32,7 @@ export const applicationFormSchema: yup.ObjectSchema = yup. .optional(), mobileNumber: yup .string() - .required(requiredMessage) + .required('Mobile Number must be defined') .test( 'not-whitespace', requiredMessage, @@ -47,7 +47,7 @@ export const applicationFormSchema: yup.ObjectSchema = yup. 'Mobile number must contain only digits, spaces, hyphens, plus signs, and parentheses', ) .max(20, 'Mobile number must be 20 characters or less') - .defined(), + .required('Mobile Number must be defined'), name: yup .string() .optional(), From f421795de651b704c076202f12afaf82c1ac74db Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Fri, 6 Mar 2026 16:19:52 +1100 Subject: [PATCH 16/18] QA fixes --- .../pages/application-form/ApplicationFormPage.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/apps/engagements/src/pages/application-form/ApplicationFormPage.tsx b/src/apps/engagements/src/pages/application-form/ApplicationFormPage.tsx index 4203b229f..89e5459bf 100644 --- a/src/apps/engagements/src/pages/application-form/ApplicationFormPage.tsx +++ b/src/apps/engagements/src/pages/application-form/ApplicationFormPage.tsx @@ -85,7 +85,7 @@ const ApplicationFormPage: FC = () => { availability: '', coverLetter: '', email: '', - mobileNumber: undefined, + mobileNumber: '', name: '', portfolioUrls: [], resumeUrl: undefined, @@ -223,7 +223,7 @@ const ApplicationFormPage: FC = () => { shouldTouch: false, shouldValidate: false, }) - setValue('mobileNumber', userData.mobileNumber, { + setValue('mobileNumber', userData.mobileNumber ?? '', { shouldDirty: false, shouldTouch: false, shouldValidate: false, @@ -422,14 +422,10 @@ const ApplicationFormPage: FC = () => { const handleMobileNumberChange = useCallback( (field: ControllerRenderProps) => ( (event: ChangeEvent): void => { - const nextValue = event.target.value - field.onChange(nextValue || undefined) - setValue('mobileNumber', nextValue === '' ? undefined : nextValue, { - shouldValidate: true, - }) + field.onChange(event.target.value) } ), - [setValue], + [], ) const handleContactFieldChange = useCallback( From d9bae99cda7416f1a9a29f26434b4a772e149464 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Tue, 10 Mar 2026 11:26:12 +1100 Subject: [PATCH 17/18] Fix for unauthorized engagements page access --- .../EngagementDetailPage.tsx | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/apps/engagements/src/pages/engagement-detail/EngagementDetailPage.tsx b/src/apps/engagements/src/pages/engagement-detail/EngagementDetailPage.tsx index 318893cee..87daa4ce2 100644 --- a/src/apps/engagements/src/pages/engagement-detail/EngagementDetailPage.tsx +++ b/src/apps/engagements/src/pages/engagement-detail/EngagementDetailPage.tsx @@ -39,6 +39,7 @@ const APPLICATION_STATUS_LABELS: Record = { } const PRIVATE_ENGAGEMENT_ROLE_KEYWORDS = ['project manager', 'task manager', 'talent manager', 'admin'] +const PRIVATE_ENGAGEMENT_ACCESS_DENIED_MESSAGE = 'You are not authorized to access this private engagement.' const formatEnumLabel = (value?: string): string | undefined => { if (!value) { @@ -209,6 +210,28 @@ const getApplicationStatusLabel = (application?: Application): string | undefine return APPLICATION_STATUS_LABELS[application.status] } +const getApiErrorMessage = (error: any): string | undefined => { + const message = error?.response?.data?.message ?? error?.data?.message ?? error?.message + + if (Array.isArray(message)) { + const firstMessage = message.find(value => typeof value === 'string' && value.trim()) + return firstMessage?.trim() + } + + return typeof message === 'string' ? message.trim() : undefined +} + +const isPrivateEngagementAccessDeniedError = (error: any): boolean => { + const status = error?.response?.status + const message = getApiErrorMessage(error) + + if (status !== 401 && status !== 403) { + return false + } + + return message === PRIVATE_ENGAGEMENT_ACCESS_DENIED_MESSAGE +} + type TermsViewData = { termsTitle: string termsBody?: string @@ -502,6 +525,7 @@ const EngagementDetailPage: FC = () => { const [engagement, setEngagement] = useState(undefined) const [loading, setLoading] = useState(true) const [error, setError] = useState(undefined) + const [privateAccessDenied, setPrivateAccessDenied] = useState(false) const [application, setApplication] = useState(undefined) const [hasApplied, setHasApplied] = useState(false) const [checkingApplication, setCheckingApplication] = useState(false) @@ -545,6 +569,7 @@ const EngagementDetailPage: FC = () => { setLoading(true) setError(undefined) + setPrivateAccessDenied(false) try { const response = await getEngagementByNanoId(nanoId) @@ -559,6 +584,11 @@ const EngagementDetailPage: FC = () => { return } + if (isPrivateEngagementAccessDeniedError(err)) { + setPrivateAccessDenied(true) + return + } + setError('Unable to load engagement details. Please try again.') } finally { setLoading(false) @@ -1009,17 +1039,7 @@ const EngagementDetailPage: FC = () => {

Private engagement

-

- {isLoggedIn - ? 'Only talent managers, project managers, administrators, ' - + 'and assigned members can view this engagement.' - : 'Sign in to confirm your access to this private engagement.'} -

- {!isLoggedIn && ( - - Sign in - - )} +

Only task managers, project managers, administrators, and assigned members can view this engagement.

) @@ -1157,6 +1177,10 @@ const EngagementDetailPage: FC = () => { return renderLoadingState() } + if (privateAccessDenied) { + return renderRestrictedEngagementState() + } + if (error) { return renderErrorState() } From ffe5009b3ee1d2ad721f0585ef271ce6754d307f Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Wed, 11 Mar 2026 08:13:23 +1100 Subject: [PATCH 18/18] Point copilot projects API requests to v6 --- src/apps/copilots/src/services/copilot-opportunities.ts | 2 +- src/apps/copilots/src/services/copilot-requests.ts | 2 +- src/apps/copilots/src/services/projects.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/apps/copilots/src/services/copilot-opportunities.ts b/src/apps/copilots/src/services/copilot-opportunities.ts index 07292b9c7..3446a9ef6 100644 --- a/src/apps/copilots/src/services/copilot-opportunities.ts +++ b/src/apps/copilots/src/services/copilot-opportunities.ts @@ -8,7 +8,7 @@ import { buildUrl } from '~/libs/shared/lib/utils/url' import { CopilotOpportunity } from '../models/CopilotOpportunity' import { CopilotApplication } from '../models/CopilotApplication' -export const copilotBaseUrl = `${EnvironmentConfig.API.V5}/projects` +export const copilotBaseUrl = `${EnvironmentConfig.API.V6}/projects` const PAGE_SIZE = 20 diff --git a/src/apps/copilots/src/services/copilot-requests.ts b/src/apps/copilots/src/services/copilot-requests.ts index 4987089b4..270452f23 100644 --- a/src/apps/copilots/src/services/copilot-requests.ts +++ b/src/apps/copilots/src/services/copilot-requests.ts @@ -9,7 +9,7 @@ import { getPaginatedAsync, PaginatedResponse } from '~/libs/core/lib/xhr/xhr-fu import { CopilotRequest } from '../models/CopilotRequest' -const baseUrl = `${EnvironmentConfig.API.V5}/projects` +const baseUrl = `${EnvironmentConfig.API.V6}/projects` const PAGE_SIZE = 20 /** diff --git a/src/apps/copilots/src/services/projects.ts b/src/apps/copilots/src/services/projects.ts index 2c31d88b7..eed8d8f54 100644 --- a/src/apps/copilots/src/services/projects.ts +++ b/src/apps/copilots/src/services/projects.ts @@ -7,7 +7,7 @@ import { EnvironmentConfig } from '~/config' import { Project } from '../models/Project' -const baseUrl = `${EnvironmentConfig.API.V5}/projects` +const baseUrl = `${EnvironmentConfig.API.V6}/projects` export type ProjectsResponse = SWRResponse