Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0d05768
PM-3921 Rephrase open to work text
himaniraghav3 Feb 19, 2026
0e8c667
Merge pull request #1485 from topcoder-platform/PM-3921
himaniraghav3 Feb 19, 2026
0c96fd2
PM-3355 Fix Rescan logic for submission
himaniraghav3 Feb 19, 2026
f46de22
Merge pull request #1486 from topcoder-platform/PM-3355
kkartunov Feb 20, 2026
ab987bd
PM-3924 Add address fields in profile app
himaniraghav3 Feb 23, 2026
a635f35
PR feedback
himaniraghav3 Feb 23, 2026
c87a9b3
Merge pull request #1488 from topcoder-platform/PM-3924
himaniraghav3 Feb 24, 2026
6ce2d5c
PM-3924 Fix admin view for location
himaniraghav3 Feb 24, 2026
ea91224
Merge pull request #1489 from topcoder-platform/PM-3924
himaniraghav3 Feb 24, 2026
f26d54c
PM-3893 Throw error for duplicate phone number
himaniraghav3 Feb 24, 2026
73e38fd
PM-3924 Fix typo
himaniraghav3 Feb 25, 2026
14b5e38
Merge pull request #1490 from topcoder-platform/PM-3893
kkartunov Feb 25, 2026
81a8658
Merge pull request #1491 from topcoder-platform/PM-3924
kkartunov Feb 25, 2026
c427399
PM-3880 Show achievements if any badges present
himaniraghav3 Feb 25, 2026
f5cfd8e
feedback
himaniraghav3 Feb 25, 2026
4386adf
Merge pull request #1492 from topcoder-platform/PM-3880
himaniraghav3 Feb 25, 2026
277aab6
PM-4004 #time 3h root cause and fixing modal not closed when cancel c…
hentrymartin Feb 25, 2026
e71dc1a
PM-4005 #time 3h fix edit tool items
hentrymartin Feb 25, 2026
bc416ae
removed console log
hentrymartin Feb 25, 2026
fa78cd7
Merge pull request #1493 from topcoder-platform/pm-4004
kkartunov Feb 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ const ServiceProvider: FC<ServiceProviderProps> = (props: ServiceProviderProps)
type='text'
error={formErrors.serviceProviderName}
dirty
forceUpdateValue

Choose a reason for hiding this comment

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

[⚠️ correctness]
The forceUpdateValue prop is added to the InputText component, but it's unclear what its purpose is without additional context. Ensure that this prop is necessary and correctly implemented in the InputText component to avoid unexpected behavior.

/>
<div className={styles.formCTAs}>
{!isEditMode && <IconOutline.PlusCircleIcon />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ const Software: FC<SoftwareProps> = (props: SoftwareProps) => {
name='softwareName'
label='Software Name *'
value={selectedSoftwareName}
forceUpdateValue

Choose a reason for hiding this comment

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

[⚠️ correctness]
The forceUpdateValue prop is being added to the InputText component. Ensure that this prop is necessary and correctly handled within the InputText component. If this prop is not defined or used within InputText, it could lead to unexpected behavior.

onChange={handleSoftwareNameChange}
placeholder='Type here the Software Name'
tabIndex={0}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ const Subscriptions: FC<SubscriptionsProps> = (props: SubscriptionsProps) => {
type='text'
error={formErrors.subscriptionName}
dirty
forceUpdateValue

Choose a reason for hiding this comment

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

[⚠️ correctness]
The forceUpdateValue prop is added to the InputText component. Ensure that this prop is necessary and correctly implemented in the InputText component. If it is not handled properly, it could lead to unexpected behavior or performance issues.

/>
<div className={styles.formCTAs}>
{!isEditMode && <IconOutline.PlusCircleIcon />}
Expand Down
3 changes: 2 additions & 1 deletion src/apps/admin/src/lib/utils/others.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export function validateS3URI(
const parsedKey = key ?? undefined
const isValid = Boolean(parsedBucket)
&& region === EnvironmentConfig.ADMIN.AWS_REGION
&& parsedBucket === EnvironmentConfig.ADMIN.AWS_DMZ_BUCKET
&& (parsedBucket === EnvironmentConfig.ADMIN.AWS_CLEAN_BUCKET
|| parsedBucket === EnvironmentConfig.ADMIN.AWS_QUARANTINE_BUCKET)

return {
bucket: parsedBucket,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,16 @@
margin-left: auto;
}
}

.tooltip {
display: flex;
margin-left: $sp-2;
position: relative;
flex-wrap: wrap;

svg:global(.tooltip-icon) {
@include icon-xl;
cursor: pointer;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react'
import { Dispatch, FC, SetStateAction, useContext, useMemo, useState } from 'react'
import { trim } from 'lodash'
import cityTimezones from 'city-timezones'
import moment from 'moment-timezone'

import { useCountryName, UserProfile } from '~/libs/core'
import { IconSolid } from '~/libs/ui'
import { profileContext, ProfileContextData, useCountryName, UserProfile, UserRole } from '~/libs/core'
import { IconOutline, IconSolid, Tooltip } from '~/libs/ui'

import { EditMemberPropertyBtn } from '../../components'

Expand All @@ -21,7 +22,9 @@ const MemberLocalInfo: FC<MemberLocalInfoProps> = (props: MemberLocalInfoProps)
const memberCountry: string | undefined
= useCountryName(props.profile?.homeCountryCode || props.profile?.competitionCountryCode)

const city: string | undefined = props.profile?.addresses?.[0]?.city
const address = props.profile?.addresses?.[0]

const city: string | undefined = address?.city

const memberCityTimezone: string | undefined = useMemo(() => {
if (!city) {
Expand All @@ -42,6 +45,10 @@ const MemberLocalInfo: FC<MemberLocalInfoProps> = (props: MemberLocalInfoProps)

const canEdit: boolean = props.authProfile?.handle === props.profile.handle

const { profile }: ProfileContextData = useContext(profileContext)

Choose a reason for hiding this comment

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

[❗❗ correctness]
The useContext hook is used to access profileContext, but there is no check to ensure that profile is defined before accessing its properties. This could lead to runtime errors if profile is undefined. Consider adding a null check or default value.

const isAdminOrTM = profile?.roles?.includes(UserRole.administrator)
|| profile?.roles?.includes(UserRole.talentManager)

const [isEditMode, setIsEditMode]: [boolean, Dispatch<SetStateAction<boolean>>]
= useState<boolean>(false)

Expand Down Expand Up @@ -76,11 +83,56 @@ const MemberLocalInfo: FC<MemberLocalInfoProps> = (props: MemberLocalInfoProps)
return 'Unknown location'
}, [city, memberCountry])

const hasDetailedAddress: boolean = useMemo(() => {
if (!address) return false
return !!(
trim(address.streetAddr1)
|| trim(address.streetAddr2)
|| trim(address.stateCode)
|| trim(address.zip)
)
}, [address])

const canSeeDetailedAddressIcon = canEdit || isAdminOrTM

const tooltipContent = useMemo(() => {

Choose a reason for hiding this comment

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

[❗❗ correctness]
The tooltipContent is conditionally rendered based on hasDetailedAddress, but it is possible for address to be undefined, which would cause trim to throw an error. Ensure that address is defined before attempting to access its properties.

if (!hasDetailedAddress) return undefined

const addressLine = [
trim(address?.streetAddr1),
trim(address?.streetAddr2),
trim(address?.city),
trim(address?.stateCode),
].filter(Boolean)
.join(', ')

const postalCode = trim(address?.zip)

if (!postalCode) return addressLine

return (
<>
<div>{addressLine}</div>
<div>
Zip/Postal code -
{postalCode}
</div>
</>
)
}, [address, hasDetailedAddress])

return (
<div className={styles.container}>
<div className={styles.localInfo}>
<IconSolid.LocationMarkerIcon />
{locationDisplay}
{hasDetailedAddress && canSeeDetailedAddressIcon && (
<div className={styles.tooltip}>
<Tooltip content={tooltipContent} triggerOn='hover'>
<IconOutline.InformationCircleIcon className='tooltip-icon' />
</Tooltip>
</div>
)}
{
canEdit && (
<EditMemberPropertyBtn
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
margin-top: $sp-4;

:global(.input-wrapper) {
margin-bottom: $sp-4;
margin-bottom: $sp-1;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react'
import { bind, omit, trim } from 'lodash'
import { omit, trim } from 'lodash'
import { toast } from 'react-toastify'
import React, { Dispatch, FC, SetStateAction, useCallback, useMemo, useState } from 'react'

import { BaseModal, Button, InputSelect, InputText } from '~/libs/ui'
import {
Expand All @@ -13,9 +13,9 @@ import {
import styles from './ModifyLocationModal.module.scss'

interface ModifyLocationModalProps {
onClose: () => void
onSave: () => void
profile: UserProfile
onClose: () => void
onSave: () => void
profile: UserProfile
}

const OMIT_ADDRESS_KEYS_ON_UPDATE = [
Expand All @@ -26,57 +26,92 @@ const ModifyLocationModal: FC<ModifyLocationModalProps> = (props: ModifyLocation
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 countries = useMemo(
() => (countryLookup || []).map((cl: CountryLookup) => ({
label: cl.country,
value: cl.countryCode,
}))
.sort((a, b) => a.label.localeCompare(b.label)),
[countryLookup],
)

const existingAddress = props.profile.addresses ? props.profile.addresses[0] : {}

Choose a reason for hiding this comment

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

[⚠️ correctness]
Consider using optional chaining (props.profile.addresses?.[0]) to access the first address. This can prevent potential runtime errors if addresses is undefined.


const [formValues, setFormValues]: [any, Dispatch<any>] = useState({

Choose a reason for hiding this comment

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

[⚠️ maintainability]
The formValues state is initialized with any type. Consider defining a specific type for formValues to improve type safety and maintainability.

country: props.profile.homeCountryCode || props.profile.competitionCountryCode,
...props.profile.addresses ? props.profile.addresses[0] : {},
country: props.profile.homeCountryCode,
...existingAddress,
})

const [formSaveError, setFormSaveError]: [
string | undefined,
Dispatch<SetStateAction<string | undefined>>
] = useState<string | undefined>()
const [formErrors, setFormErrors]: [
{ [key: string]: string },
Dispatch<SetStateAction<{ [key: string]: string }>>
] = useState({})

const [isSaving, setIsSaving]: [boolean, Dispatch<SetStateAction<boolean>>]
= useState<boolean>(false)
const [formSaveError, setFormSaveError] = useState<string | undefined>()
const [isSaving, setIsSaving] = useState(false)
const [isFormChanged, setIsFormChanged] = useState(false)

const [isFormChanged, setIsFormChanged]: [boolean, Dispatch<SetStateAction<boolean>>]
= useState<boolean>(false)
const handleFormValueChange = useCallback(
(key: string) => (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>): void => {

Choose a reason for hiding this comment

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

[❗❗ correctness]
The handleFormValueChange function uses formValues from the outer scope, which can lead to stale state issues. Consider using a functional state update to ensure the latest state is used.

const value: string = event.target.value

function handleFormValueChange(key: string, event: React.ChangeEvent<HTMLInputElement>): void {
const oldFormValues = { ...formValues }
setFormValues({
...formValues,
[key]: value,
})

setFormValues({
...oldFormValues,
[key]: event.target.value,
})
setIsFormChanged(true)
setIsFormChanged(true)

setFormErrors(prev => {
if (!prev[key]) return prev
const next = { ...prev }
delete next[key]
return next
})
},
[formValues],
)

function validate(): boolean {
const nextErrors: { [key: string]: string } = {}

if (!trim(formValues.city)) nextErrors.city = 'Please select a city'
if (!formValues.country) nextErrors.country = 'Please select a country'

setFormErrors(nextErrors)
return Object.keys(nextErrors).length === 0
}

function handleLocationSave(): void {
updateMemberProfileAsync(
props.profile.handle,
{
addresses: [{
...props.profile.addresses ? omit(props.profile.addresses[0], OMIT_ADDRESS_KEYS_ON_UPDATE) : {},
if (!validate()) return

setIsSaving(true)

Choose a reason for hiding this comment

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

[⚠️ correctness]
The setIsSaving(true) call should be placed after the validate() check to avoid setting the saving state unnecessarily when validation fails.


const baseAddressPayload = props.profile.addresses
? omit(props.profile.addresses[0], OMIT_ADDRESS_KEYS_ON_UPDATE)
: {}

updateMemberProfileAsync(props.profile.handle, {
addresses: [
{
...baseAddressPayload,
city: trim(formValues.city),
}],
competitionCountryCode: formValues.country,
homeCountryCode: formValues.country,
},
)
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 location has been updated.', { position: toast.POSITION.BOTTOM_RIGHT })
props.onSave()
})
.catch((error: any) => {

Choose a reason for hiding this comment

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

[💡 maintainability]
Consider handling specific error types in the catch block to provide more informative error messages to the user.

toast.error('Something went wrong. Please try again.', { position: toast.POSITION.BOTTOM_RIGHT })
setFormSaveError(error.message || error)
setFormSaveError(error?.message || error)
})
.finally(() => {
setIsFormChanged(false)
Expand Down Expand Up @@ -111,23 +146,77 @@ const ModifyLocationModal: FC<ModifyLocationModalProps> = (props: ModifyLocation
)}
>
<p>Provide details on your location.</p>

<form className={styles.editForm}>
<InputText
label='City'
name='address'
label='Address'
error={formErrors.streetAddr1}
placeholder='Your address'
dirty
tabIndex={0}
type='text'
onChange={handleFormValueChange('streetAddr1')}
value={formValues.streetAddr1}
/>

<InputText
name='address2'
label='Address 2'
error={formErrors.streetAddr2}
placeholder='Your address continued'
dirty
tabIndex={0}
type='text'
onChange={handleFormValueChange('streetAddr2')}
value={formValues.streetAddr2}
/>

<InputText
name='city'
onChange={bind(handleFormValueChange, this, 'city')}
label='City *'
error={formErrors.city}
placeholder='Which city do you live in?'
dirty
tabIndex={0}
type='text'
onChange={handleFormValueChange('city')}
value={formValues.city}
/>

<InputText
name='state'
label='State'
error={formErrors.stateCode}
placeholder='State'
dirty
tabIndex={0}
type='text'
placeholder='Select your city name'
onChange={handleFormValueChange('stateCode')}
value={formValues.stateCode}
/>

<InputText
name='zip'
label='Zip/Postal Code'
error={formErrors.zip}
placeholder='Your Zip or Postal Code'
dirty
tabIndex={0}
type='text'
onChange={handleFormValueChange('zip')}
value={formValues.zip}
/>

<InputSelect
options={contries}
options={countries}
value={formValues.country}
onChange={bind(handleFormValueChange, this, 'country')}
onChange={handleFormValueChange('country')}
name='country'
label='Country *'
error={formErrors.country}
placeholder='Select a Country'
dirty
/>
</form>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,13 @@ const ModifyPhonesModal: FC<ModifyPhonesModalProps> = (props: ModifyPhonesModalP
toast.success('Phone numbers updated successfully.', { position: toast.POSITION.BOTTOM_RIGHT })
props.onSave()
})
.catch(() => {
toast.error('Failed to update phone numbers.', { position: toast.POSITION.BOTTOM_RIGHT })
.catch(error => {
const apiMessage

Choose a reason for hiding this comment

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

[❗❗ correctness]
The variable apiMessage is declared without an initializer. This could lead to a ReferenceError if error?.message is undefined. Consider initializing apiMessage directly with the conditional expression.

= error?.message || 'Failed to update phone numbers.'

toast.error(apiMessage, {
position: toast.POSITION.BOTTOM_RIGHT,
})
setIsSaving(false)
})
}
Expand Down
Loading
Loading