Skip to content

Comments

[PROD RELEASE] - Profile app fixes & sys-admin tweaks#1463

Merged
kkartunov merged 12 commits intomasterfrom
dev
Feb 9, 2026
Merged

[PROD RELEASE] - Profile app fixes & sys-admin tweaks#1463
kkartunov merged 12 commits intomasterfrom
dev

Conversation

@kkartunov
Copy link
Collaborator

Related JIRA Ticket:

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

What's in this PR?

@kkartunov kkartunov requested a review from jmgasper as a code owner February 9, 2026 09:14
gap: $sp-3;
}

.selectWrapper {
Copy link

Choose a reason for hiding this comment

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

[⚠️ design]
The use of min-width: 360px; for .selectWrapper might cause layout issues on smaller screens. Consider using a responsive approach to ensure better adaptability across different screen sizes.

.loadingSpinnerContainer {
position: relative;
height: 100px;
margin-top: -30px;
Copy link

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The negative margin margin-top: -30px; in .loadingSpinnerContainer can lead to unexpected layout shifts, especially if the surrounding elements change. Consider using a different layout strategy to avoid relying on negative margins.

@kkartunov kkartunov changed the title [PROD RELEASE] - Profile app fixes [PROD RELEASE] - Profile app fixes & sys-admin tweaks Feb 9, 2026

do {
// eslint-disable-next-line no-await-in-loop
const response = await getChallengeResources(challengeId, {
Copy link

Choose a reason for hiding this comment

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

[⚠️ performance]
Using no-await-in-loop can lead to performance issues as it waits for each iteration to complete before proceeding. Consider refactoring to use Promise.all for concurrent execution.

}

const statusAfterUpdate = payload.status || challengeInfo.status
if (winnerPayload.length > 0 && statusAfterUpdate !== 'COMPLETED') {
Copy link

Choose a reason for hiding this comment

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

[⚠️ correctness]
The logic here assumes that the challenge status must be 'COMPLETED' if there are winners. This assumption should be validated with business requirements to ensure correctness.

<a
href={`${EnvironmentConfig.API.V6}/challenges/${challenge.id}`}
<Link
to={`${challenge.id}`}
Copy link

Choose a reason for hiding this comment

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

[⚠️ correctness]
The to prop in the Link component should be a valid path string. Ensure that ${challenge.id} resolves to a valid route. Consider using a more descriptive route path if applicable.

abbreviation: string
}

export type ChallengeWinner = {
Copy link

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Consider using string for userId in ChallengeWinner to maintain consistency with other ID fields like id and typeId in the Challenge interface, which are strings. This could help avoid potential type mismatches when interacting with other parts of the system.

export type ChallengePrize = {
description?: string
type: string
value: number
Copy 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 value field in ChallengePrize is validated to prevent negative values, which may not make sense in the context of a prize.

type UpdateChallengePayload = {
status?: Challenge['status']
winners?: Array<
Pick<ChallengeWinner, 'handle' | 'placement' | 'userId'>
Copy link

Choose a reason for hiding this comment

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

[⚠️ correctness]
Consider validating the winners array to ensure that each winner has a unique placement value. This will prevent potential data inconsistencies where multiple winners could have the same placement.

export const updateChallengeById = async (
id: Challenge['id'],
data: UpdateChallengePayload,
): Promise<Challenge> => xhrPatchAsync<UpdateChallengePayload, Challenge>(
Copy link

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Ensure that the xhrPatchAsync function handles potential errors gracefully, such as network failures or server errors, and that these are communicated back to the caller appropriately.

),
personalizationInfo,
]
const saveData: any = async () => {
Copy link

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The saveData function is typed as any. Consider specifying a more precise type to improve type safety and maintainability.

} else {
await updateMemberPersonalizations(datas)
try {
await updateOrCreateMemberTraitsAsync(profileHandle || '', [{
Copy link

Choose a reason for hiding this comment

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

[❗❗ correctness]
The profileHandle is used with a fallback to an empty string. If profileHandle is undefined, this could lead to unexpected behavior when calling updateOrCreateMemberTraitsAsync. Consider validating profileHandle before use.

@@ -1,14 +1,21 @@
export interface OpenToWorkTrait {
Copy link

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The OpenToWorkTrait interface is defined but not used within this file. Consider removing it if it's not needed, or ensure it's utilized appropriately elsewhere.

referAs?: string
profileSelfTitle?: string
shortBio?: string
links?: Array<{ url: string; name: string }>
Copy link

Choose a reason for hiding this comment

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

[💡 design]
Consider defining a separate type or interface for the links array elements to improve reusability and clarity.

if (!memberPersonalizationTraits) return

const personalizationData = memberPersonalizationTraits?.[0]?.traits?.data || []
const personalizationData = memberPersonalizationTraits?.[0]?.traits?.data?.[0] || {}
Copy link

Choose a reason for hiding this comment

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

[❗❗ correctness]
The change from find to direct access assumes that memberPersonalizationTraits?.[0]?.traits?.data always contains an object with the expected structure. This could lead to runtime errors if the data structure changes or if the data is not as expected. Consider adding validation or error handling to ensure the data structure is correct.

setLoading(true)

const existingData = memberPersonalizationTraits?.[0]?.traits?.data || []
const existing = memberPersonalizationTraits?.[0]?.traits?.data?.[0] || {}
Copy link

Choose a reason for hiding this comment

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

[❗❗ correctness]
The change from existingData to existing assumes that memberPersonalizationTraits?.[0]?.traits?.data?.[0] is always an object. If this assumption is incorrect, it could lead to runtime errors. Consider adding checks or handling cases where the data might not be structured as expected.


const nextPersonalizations = personalizationTrait?.traits?.data

if (Array.isArray(nextPersonalizations)) {
Copy link

Choose a reason for hiding this comment

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

[💡 maintainability]
The check Array.isArray(nextPersonalizations) is good, but consider logging or handling the case where nextPersonalizations is not an array to aid in debugging potential issues.

setPersonalizationInfo({
...(personalizationInfo || {}),
setPersonalizationInfo(prev => ({
...(prev ?? props.reduxPersonalizations ?? {}),
Copy link

Choose a reason for hiding this comment

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

[⚠️ correctness]
Using prev ?? props.reduxPersonalizations ?? {} in setPersonalizationInfo could lead to unexpected behavior if prev is null or undefined. Consider ensuring prev is always an object to avoid potential issues with object spreading.

availability,
preferredRoles,
},
openToWork: openToWork
Copy link

Choose a reason for hiding this comment

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

[💡 correctness]
The use of _.omitBy with _.isUndefined is appropriate for filtering out undefined values, but consider if there are any other falsy values that should also be omitted. If not, this is fine as is.

}

function isObjectLike(value: any): boolean {
return !!value && typeof value === 'object'
Copy link

Choose a reason for hiding this comment

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

[⚠️ correctness]
The isObjectLike function checks if a value is an object but does not exclude arrays, which are also objects in JavaScript. If the intention is to exclude arrays, consider using typeof value === 'object' && !Array.isArray(value).

return !!value && typeof value === 'object'
}

export function flattenPersonalizationData(personalizationData: UserTrait[] = []): UserTrait[] {
Copy link

Choose a reason for hiding this comment

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

[⚠️ correctness]
The flattenPersonalizationData function assumes that item.personalization is an array if it exists. Consider adding a type guard to ensure item.personalization is an array before iterating over it.

.find(Boolean)
}

export function getPersonalizationLinks(personalizationData: UserTrait[] = []): UserTrait[] {
Copy link

Choose a reason for hiding this comment

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

[⚠️ correctness]
In getPersonalizationLinks, the linksByKey set is used to deduplicate links by name and URL. This approach may not handle cases where URLs differ only by trailing slashes or query parameters. Consider normalizing URLs before deduplication.


const memberTitleTrait: any
= memberPersonalizationTraits?.[0]?.traits?.data?.find((trait: any) => trait.profileSelfTitle)
const memberTitle: string | undefined = useMemo(
Copy link

Choose a reason for hiding this comment

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

[⚠️ correctness]
The use of useMemo here is appropriate for memoizing the memberTitle, but ensure that getFirstProfileSelfTitle is a pure function. If it has side effects or depends on anything other than its arguments, it could lead to unexpected behavior.

(trait: any) => trait.profileSelfTitle,
)
setMemberTitle(profileSelfTitleData?.profileSelfTitle)
setMemberTitle(getFirstProfileSelfTitle(props.memberPersonalizationTraitsData))
Copy link

Choose a reason for hiding this comment

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

[❗❗ correctness]
The function getFirstProfileSelfTitle is used here to set the member title. Ensure that this function handles cases where memberPersonalizationTraitsData is undefined or empty to avoid potential runtime errors.

updatedTitle,
props.memberPersonalizationTraitsData,
)
const existing = props.memberPersonalizationTraitsData?.[0] || {}
Copy link

Choose a reason for hiding this comment

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

[⚠️ correctness]
The assignment const existing = props.memberPersonalizationTraitsData?.[0] || {} assumes that the first element is the one to update. Ensure this logic aligns with the intended behavior, as it might overwrite data if memberPersonalizationTraitsData contains multiple entries.

(trait: UserTrait) => trait.links,
), [memberPersonalizationTraits])
const memberLinks: UserTrait[] = useMemo(
() => getPersonalizationLinks(memberPersonalizationTraits?.[0]?.traits?.data),
Copy link

Choose a reason for hiding this comment

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

[❗❗ correctness]
The getPersonalizationLinks function is now responsible for extracting links from memberPersonalizationTraits. Ensure that this function handles cases where memberPersonalizationTraits is undefined or does not contain the expected structure to avoid runtime errors.

<div className={styles.links}>
{
memberLinks?.links.map((trait: UserTrait) => (
memberLinks.map((trait: UserTrait) => (
Copy link

Choose a reason for hiding this comment

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

[⚠️ correctness]
The key prop for the list items is constructed using trait.name and trait.url. Ensure that both name and url are unique and consistently available to prevent potential key collisions or rendering issues.

updatedLinks,
props.memberPersonalizationTraitsFullData ?? [],
)
const existing = props.memberPersonalizationTraitsFullData?.[0] || {}
Copy link

Choose a reason for hiding this comment

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

[⚠️ correctness]
The existing variable is assigned the first element of memberPersonalizationTraitsFullData or an empty object. Ensure that memberPersonalizationTraitsFullData is always an array or handle the case where it might be undefined or not an array to avoid potential runtime errors.

if (!memberPersonalizationTraits) return

const personalizationData = memberPersonalizationTraits?.[0]?.traits?.data || []
const personalizationData = memberPersonalizationTraits?.[0]?.traits?.data?.[0] || {}
Copy link

Choose a reason for hiding this comment

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

[❗❗ correctness]
The change from find to direct access assumes that memberPersonalizationTraits?.[0]?.traits?.data always contains an object with the openToWork property at index 0. Ensure that this assumption is valid, as it could lead to runtime errors if the structure of data changes or if it is empty.

setIsSaving(true)

const existingData = memberPersonalizationTraits?.[0]?.traits?.data || []
const existing = memberPersonalizationTraits?.[0]?.traits?.data?.[0] || {}
Copy link

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The change from using upsertTrait to manually constructing the personalizationData array removes the abstraction provided by upsertTrait. This could lead to potential errors if the logic for updating traits changes in the future. Consider whether maintaining the use of upsertTrait would improve maintainability.

if (
!hasOpenToWork
|| !props.profile.availableForGigs
|| (openToWorkItem.preferredRoles?.length === 0 && openToWorkItem.availability === undefined)) return <></>
Copy link

Choose a reason for hiding this comment

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

[💡 readability]
The condition (openToWorkItem.preferredRoles?.length === 0 && openToWorkItem.availability === undefined) could be simplified to !openToWorkItem.preferredRoles?.length && openToWorkItem.availability === undefined for better readability.

@kkartunov kkartunov merged commit 9ac441d into master Feb 9, 2026
12 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.

3 participants