Skip to content

Conversation

@kkartunov
Copy link
Collaborator

Related JIRA Ticket:

https://topcoder.atlassian.net/browse/

What's in this PR?

vas3a and others added 25 commits January 13, 2026 14:50
…eteness

PM-3357 - profile completeness messaging
…al-score

For approvers show final score if available
…eteness

PM-3357 - make sure to filter out profileCompleteness categories
PM-3397 Add email to profiles UI
feat(PM-3398): added description and associated skills field in Add/Update Work experience modal, work card in Profile Page
feat(PM-3319): added download button in profile page
@kkartunov kkartunov requested a review from jmgasper as a code owner January 16, 2026 06:09
LeaveStatus.LEAVE,
LeaveStatus.HOLIDAY,
LeaveStatus.WIPRO_HOLIDAY,
LeaveStatus.WEEKEND,

Choose a reason for hiding this comment

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

[❗❗ correctness]
Removing LeaveStatus.WIPRO_HOLIDAY from legendStatusOrder may affect any functionality that relies on the order of statuses. Ensure that this change is intentional and does not break any dependent logic.

}

// Check if user has admin roles
if (authProfile.roles?.some(role => ADMIN_ROLES.includes(role.toLowerCase() as UserRole))) {

Choose a reason for hiding this comment

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

[⚠️ correctness]
The use of toLowerCase() on roles might lead to unexpected behavior if ADMIN_ROLES contains roles with mixed casing. Consider ensuring ADMIN_ROLES is consistently cased or adjust the logic to handle casing more robustly.

const allowedRoles = ['Project Manager', 'Talent Manager']
if (authProfile
.roles?.some(
role => allowedRoles.some(allowed => role.toLowerCase() === allowed.toLowerCase()),

Choose a reason for hiding this comment

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

[💡 performance]
The allowedRoles array is defined within the function and used only once. Consider moving it outside the function to avoid redefining it on each function call, which can slightly improve performance.

import classNames from 'classnames'

import { NamesAndHandleAppearance, useMemberTraits, UserProfile, UserTraitIds, UserTraits } from '~/libs/core'
import { CopyButton } from '~/apps/admin/src/lib/components/CopyButton'

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The CopyButton component is imported from the admin app's library. Ensure that this component is intended to be shared across different apps and that there are no unintended dependencies or side effects. Consider moving shared components to a common library if they are used across multiple apps.

)
}

{props.profile?.email

Choose a reason for hiding this comment

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

[❗❗ security]
Displaying the user's email address directly in the UI may have privacy implications. Ensure that displaying the email is compliant with privacy policies and consider adding user consent or a privacy notice if necessary.

.downloadButtonWrap {
position: absolute;
top: $sp-4;
right: calc((100% - #{$xxl-min}) / 2);

Choose a reason for hiding this comment

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

[⚠️ correctness]
The calculation for right using calc((100% - #{$xxl-min}) / 2) may lead to unexpected layout issues if $xxl-min is larger than the container width. Consider verifying the value of $xxl-min or using a different approach to ensure consistent alignment.

right: $sp-4;
}

@include ltelg {

Choose a reason for hiding this comment

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

[💡 maintainability]
The @include ltelg block redefines several properties that are already set outside of this block. This could lead to redundancy and potential maintenance issues. Consider consolidating these styles to avoid duplication.

setIsDownloading(true)
try {
await downloadProfileAsync(props.profile.handle)
} catch (error) {} finally {

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Consider logging the error in the catch block to aid in debugging and monitoring potential issues during the profile download process.


setIsDownloading(true)
try {
await downloadProfileAsync(props.profile.handle)

Choose a reason for hiding this comment

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

[❗❗ correctness]
Ensure that downloadProfileAsync handles all potential errors, such as network failures or invalid responses, to prevent unhandled promise rejections.


useEffect(() => { completeness?.mutate() }, [props.profile])

const [count, incompleteEntries] = useMemo(() => {

Choose a reason for hiding this comment

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

[💡 performance]
Using useMemo here is unnecessary unless the computation of fields is expensive or the component re-renders frequently. Consider removing useMemo if performance is not a concern, as it adds complexity without clear benefit.

useEffect(() => { completeness?.mutate() }, [props.profile])

const [count, incompleteEntries] = useMemo(() => {
const fields = Object.entries(completeness.entries)

Choose a reason for hiding this comment

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

[💡 maintainability]
The use of Object.entries followed by filtering and mapping could be simplified if completeness.entries is guaranteed to be an object with known keys. Consider using a more direct approach if possible.

const [formValues, setFormValues]: [
{ [key: string]: string | boolean | Date | undefined },
Dispatch<SetStateAction<{ [key: string]: string | boolean | Date | undefined }>>
{ [key: string]: string | boolean | Date | any[] | undefined },

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Using any[] in the type definition for formValues can lead to potential type safety issues. Consider defining a more specific type for the array elements to improve type safety and maintainability.

})
}

function handleSkillsChange(event: ChangeEvent<HTMLInputElement>): void {

Choose a reason for hiding this comment

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

[⚠️ correctness]
The handleSkillsChange function uses any for the event target value. Consider using a more specific type to improve type safety and prevent potential runtime errors.


const updatedWorkExpirence: UserTrait = {
cityTown: formValues.city,
associatedSkills: (formValues.associatedSkills as any[])?.map((s: any) => s.id || s) || [],

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The use of any[] for associatedSkills in updatedWorkExpirence could lead to type safety issues. Consider defining a more specific type for skill objects to ensure consistency and prevent errors.


setEditedItemIndex(indx)

let associatedSkills: any[] = []

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The associatedSkills array is typed as any[]. Consider defining a specific type for skill objects to improve type safety and maintainability.

= useMemo(() => memberWorkExpirenceTraits?.[0]?.traits?.data, [memberWorkExpirenceTraits])

// Collect all unique skill IDs from work experience entries
const allSkillIds = useMemo(() => {

Choose a reason for hiding this comment

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

[💡 maintainability]
The use of useMemo here is appropriate for memoizing the list of skill IDs. However, consider adding a type annotation to the returned array for better type safety and clarity.

return Array.from(skillIdsSet)
}, [workExpirence])

const { data: fetchedSkills, error: skillsError }: SWRResponse<UserSkill[], Error> = useSkillsByIds(

Choose a reason for hiding this comment

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

[⚠️ correctness]
The useSkillsByIds hook is called with undefined if allSkillIds is empty. Ensure that useSkillsByIds can handle undefined gracefully without causing errors.

return map
}, [fetchedSkills, allSkillIds])

const areSkillsLoaded = useCallback((work: UserTrait): boolean => {

Choose a reason for hiding this comment

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

[💡 performance]
The useCallback hook is used to memoize the areSkillsLoaded function. Ensure that this function is not being re-created unnecessarily by verifying that skillNamesMap is stable and does not change frequently.

<WorkExpirenceCard
key={uniqueKey || `${work.position || 'experience'}-${index}`}
work={work}
skillNamesMap={skillNamesMap}

Choose a reason for hiding this comment

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

[⚠️ performance]
Passing skillNamesMap as a prop to WorkExpirenceCard could lead to unnecessary re-renders if skillNamesMap changes frequently. Ensure that skillNamesMap is memoized properly to prevent performance issues.

align-items: flex-end;
}

.workExpirenceCardDescription {

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The class name .workExpirenceCardDescription contains a typo. It should be .workExperienceCardDescription to maintain consistency and avoid confusion.

}
}

.workExpirenceCardSkills {

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The class name .workExpirenceCardSkills contains a typo. It should be .workExperienceCardSkills to maintain consistency and avoid confusion.

@@ -1,3 +1,5 @@
/* eslint-disable complexity */

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Disabling complexity checks can hide potential issues in the code. Consider refactoring the component to reduce complexity instead of disabling the rule.

<div className={styles.workExpirenceCardSkills}>
<p className='body-main-small-bold'>Skills:</p>
<div className={styles.skillsList}>
{props.work.associatedSkills.map((skillId: string) => {

Choose a reason for hiding this comment

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

[❗❗ correctness]
The skillNamesMap is accessed without checking if it is defined, which could lead to runtime errors if skillNamesMap is undefined. Consider using optional chaining or a default value to ensure safety.

}

return <span>{rawScoreDisplay}</span>
return <span>{rawScoreDisplay || aggregateScore}</span>

Choose a reason for hiding this comment

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

[⚠️ correctness]
Using rawScoreDisplay || aggregateScore could lead to unexpected behavior if rawScoreDisplay is a falsy value like 0. Consider explicitly checking for undefined or null to ensure the correct value is displayed.


const percentComplete = data?.data?.percentComplete
return {
entries: omit(data?.data ?? {}, 'percentComplete'),

Choose a reason for hiding this comment

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

[⚠️ performance]
The use of omit from lodash is generally fine, but consider the potential performance impact if data?.data contains a large number of keys. If performance becomes an issue, you might want to explore more efficient ways to handle this.

link.click()
link.parentNode?.removeChild(link)
} catch (error) {
console.error('Failed to download profile:', error)

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Consider providing more detailed error handling or logging for the downloadProfileAsync function. Currently, the catch block only logs a generic error message, which might not be sufficient for troubleshooting specific issues.

link.setAttribute('download', `profile-${handle}.pdf`)
document.body.appendChild(link)
link.click()
link.parentNode?.removeChild(link)

Choose a reason for hiding this comment

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

[⚠️ correctness]
Ensure that the link element is always removed from the DOM, even if an error occurs during the link.click() operation. Consider moving the link.parentNode?.removeChild(link) line into a finally block to guarantee cleanup.

}

try {
const skillPromises = skillIds.map(skillId => xhrGetAsync<UserSkill>(`${baseUrl}/skills/${skillId}`)

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Consider handling specific errors instead of catching all errors with a generic catch. This will help in diagnosing issues more effectively and maintaining the code.


return useSWR<UserSkill[], Error>(
swrKey,
() => fetchSkillsByIdsFetcher(skillIds!),

Choose a reason for hiding this comment

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

[❗❗ correctness]
Using the non-null assertion operator (!) on skillIds assumes that swrKey will never be undefined. Ensure that this assumption is valid or handle the case where skillIds might be undefined to avoid potential runtime errors.

@kkartunov kkartunov merged commit e3ad21d into engagements Jan 16, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants