Skip to content

Commit 211d84d

Browse files
authored
Merge pull request #2152 from topcoder-platform/dev
Update opportunities-v6 with dev branch
2 parents 3099a96 + 03b20d6 commit 211d84d

48 files changed

Lines changed: 2795 additions & 250 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
@import "@libs/ui/styles/includes";
2+
3+
.container {
4+
min-height: 60vh;
5+
display: flex;
6+
align-items: center;
7+
justify-content: center;
8+
}
9+
10+
.content {
11+
display: flex;
12+
flex-direction: column;
13+
align-items: center;
14+
justify-content: center;
15+
text-align: center;
16+
gap: $sp-6;
17+
padding: $sp-15 $sp-8;
18+
color: $black-100;
19+
20+
svg {
21+
color: $black-60;
22+
}
23+
}
24+
25+
.title {
26+
@include font-barlow;
27+
font-weight: 600;
28+
font-size: 28px;
29+
line-height: 34px;
30+
margin: 0;
31+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { FC, useCallback } from 'react'
2+
import { useNavigate } from 'react-router-dom'
3+
4+
import { Button, ContentLayout, IconOutline, IconSolid, PageTitle } from '~/libs/ui'
5+
6+
import styles from './MemberNotFound.module.scss'
7+
8+
interface MemberNotFoundProps {
9+
memberHandle?: string
10+
}
11+
12+
const MemberNotFound: FC<MemberNotFoundProps> = (props: MemberNotFoundProps) => {
13+
const navigate = useNavigate()
14+
15+
const handleBack = useCallback(() => {
16+
navigate(-1)
17+
}, [navigate])
18+
19+
return (
20+
<ContentLayout outerClass={styles.container}>
21+
<PageTitle>Profile Not Found | Topcoder</PageTitle>
22+
23+
<div className={styles.content} role='alert'>
24+
<IconOutline.ExclamationCircleIcon className='icon-xxxl' />
25+
<h2 className={styles.title}>We were unable to locate that profile</h2>
26+
{props.memberHandle && (
27+
<p className='body-main'>
28+
No member was found with the handle
29+
{' '}
30+
<strong>{props.memberHandle}</strong>
31+
.
32+
</p>
33+
)}
34+
<Button
35+
link
36+
size='lg'
37+
iconToLeft
38+
icon={IconSolid.ArrowLeftIcon}
39+
onClick={handleBack}
40+
>
41+
Go back
42+
</Button>
43+
</div>
44+
</ContentLayout>
45+
)
46+
}
47+
48+
export default MemberNotFound
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as MemberNotFound } from './MemberNotFound'

src/apps/profiles/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export * from './tc-achievements/SRMView/ChallengesGrid'
44
export * from './EditMemberPropertyBtn'
55
export * from './AddButton'
66
export * from './EmptySection'
7+
export * from './MemberNotFound'

src/apps/profiles/src/member-badges/MemberBadgesPage.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { Dispatch, FC, SetStateAction, useCallback, useEffect, useState } from 'react'
22
import { Params, useNavigate, useParams } from 'react-router-dom'
3+
import { AxiosError } from 'axios'
34
import { bind } from 'lodash'
45

56
import { profileGetPublicAsync, useMemberBadges, UserBadge, UserBadgesResponse, UserProfile } from '~/libs/core'
67
import { Button, ContentLayout, IconSolid, LoadingSpinner } from '~/libs/ui'
78

8-
import { MemberBadgeModal } from '../components'
9+
import { MemberBadgeModal, MemberNotFound } from '../components'
910

1011
import styles from './MemberBadgesPage.module.scss'
1112

@@ -19,6 +20,7 @@ const MemberBadgesPage: FC<{}> = () => {
1920
] = useState()
2021

2122
const [profileReady, setProfileReady]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false)
23+
const [notFound, setNotFound]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false)
2224

2325
const memberBadges: UserBadgesResponse | undefined = useMemberBadges(profile?.userId as number, { limit: 100 })
2426

@@ -39,12 +41,21 @@ const MemberBadgesPage: FC<{}> = () => {
3941

4042
useEffect(() => {
4143
if (routeParams.memberHandle) {
44+
setProfileReady(false)
45+
setNotFound(false)
46+
setProfile(undefined)
47+
4248
profileGetPublicAsync(routeParams.memberHandle)
4349
.then(userProfile => {
4450
setProfile(userProfile)
4551
setProfileReady(true)
4652
})
47-
// TODO: NOT FOUND PAGE redirect/dispaly via catch
53+
.catch((e: AxiosError) => {
54+
if (e.code === AxiosError.ERR_BAD_REQUEST && e.response?.status === 404) {
55+
setNotFound(true)
56+
setProfileReady(true)
57+
}
58+
})
4859
}
4960
}, [routeParams.memberHandle])
5061

@@ -54,9 +65,13 @@ const MemberBadgesPage: FC<{}> = () => {
5465

5566
return (
5667
<>
57-
<LoadingSpinner hide={profileReady && !!memberBadges} />
68+
<LoadingSpinner hide={profileReady && (notFound || !!memberBadges)} />
69+
70+
{profileReady && notFound && (
71+
<MemberNotFound memberHandle={routeParams.memberHandle} />
72+
)}
5873

59-
{profileReady && profile && !!memberBadges && (
74+
{profileReady && profile && !notFound && !!memberBadges && (
6075
<ContentLayout
6176
outerClass={styles.container}
6277
>

src/apps/profiles/src/member-profile/MemberProfilePage.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { AxiosError } from 'axios'
55
import { profileContext, ProfileContextData, profileGetPublicAsync, UserProfile } from '~/libs/core'
66
import { LoadingSpinner } from '~/libs/ui'
77

8-
import { rootRoute } from '../profiles.routes'
8+
import { MemberNotFound } from '../components'
99
import { notifyUniNavi } from '../lib'
1010

1111
import { ProfilePageLayout } from './page-layout'
@@ -21,6 +21,7 @@ const MemberProfilePage: FC<{}> = () => {
2121
] = useState()
2222

2323
const [profileReady, setProfileReady]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false)
24+
const [notFound, setNotFound]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false)
2425
const { isTalentSearch }: MemberProfileContextValue = useMemberProfileContext()
2526

2627
const { profile: authProfile }: ProfileContextData = useContext(profileContext)
@@ -31,14 +32,19 @@ const MemberProfilePage: FC<{}> = () => {
3132

3233
useEffect(() => {
3334
if (routeParams.memberHandle) {
35+
setProfileReady(false)
36+
setNotFound(false)
37+
setProfile(undefined)
38+
3439
profileGetPublicAsync(routeParams.memberHandle)
3540
.then(userProfile => {
3641
setProfile({ ...userProfile } as UserProfile)
3742
setProfileReady(true)
3843
})
3944
.catch((e: AxiosError) => {
4045
if (e.code === AxiosError.ERR_BAD_REQUEST && e.response?.status === 404) {
41-
navigate(rootRoute)
46+
setNotFound(true)
47+
setProfileReady(true)
4248
}
4349
})
4450
}
@@ -58,7 +64,11 @@ const MemberProfilePage: FC<{}> = () => {
5864
<>
5965
<LoadingSpinner hide={profileReady} />
6066

61-
{profileReady && profile && (
67+
{profileReady && notFound && (
68+
<MemberNotFound memberHandle={routeParams.memberHandle} />
69+
)}
70+
71+
{profileReady && profile && !notFound && (
6272
<ProfilePageLayout
6373
handleBackBtn={handleBackBtn}
6474
isTalentSearch={isTalentSearch}

src/apps/profiles/src/member-profile/about-me/MemberRatingCard/MemberRatingInfoModal/MemberRatingInfoModal.spec.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,23 @@ describe('MemberRatingInfoModal', () => {
212212
expect(parseFloat(marker.style.left))
213213
.toBeCloseTo((3.5 / 6) * 100)
214214
})
215+
216+
it('hides the 2200+ axis label when the distribution has no elite buckets', () => {
217+
render(
218+
<MemberRatingInfoModal
219+
audienceLabel='developers'
220+
onClose={jest.fn()}
221+
percentile={15}
222+
profile={baseProfile}
223+
rating={1646}
224+
ratingDistribution={ratingDistribution}
225+
/>,
226+
)
227+
228+
expect(screen.getByText('1500'))
229+
.toBeInTheDocument()
230+
expect(screen.queryByText('2200+'))
231+
.not
232+
.toBeInTheDocument()
233+
})
215234
})

src/apps/profiles/src/member-profile/about-me/MemberRatingCard/MemberRatingInfoModal/MemberRatingInfoModal.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,29 @@ const getAxisLabelPosition = (
292292
return 100
293293
}
294294

295+
/**
296+
* Returns axis labels that fall within the visible distribution ranges.
297+
*
298+
* Hides labels such as `2200+` when the trimmed histogram has no elite buckets,
299+
* which otherwise stack on top of the final label (e.g. `1500`).
300+
*
301+
* @param {RatingDistributionRange[]} ranges - Visible rating distribution ranges.
302+
* @returns {Array<{ label: string, value: number }>} Axis labels to render.
303+
*/
304+
const getVisibleAxisLabels = (
305+
ranges: RatingDistributionRange[],
306+
): Array<{ label: string, value: number }> => {
307+
if (ranges.length === 0) {
308+
return chartAxisLabels
309+
}
310+
311+
const chartEnd = ranges[ranges.length - 1].end
312+
313+
return chartAxisLabels.filter((axisLabel: { label: string, value: number }) => (
314+
axisLabel.value <= chartEnd
315+
))
316+
}
317+
295318
/**
296319
* Calculates a bar height for a histogram count.
297320
*
@@ -359,6 +382,9 @@ const MemberRatingInfoModal: FC<MemberRatingInfoModalProps> = (props: MemberRati
359382
const markerPosition: number = props.rating !== undefined
360383
? getMarkerPosition(props.rating, distributionRanges)
361384
: 0
385+
const visibleAxisLabels: Array<{ label: string, value: number }> = useMemo(() => (
386+
getVisibleAxisLabels(distributionRanges)
387+
), [distributionRanges])
362388
const shouldStackMarkerRating: boolean = props.rating !== undefined && (
363389
markerPosition >= stackedMarkerPositionThreshold
364390
|| props.rating >= stackedMarkerRatingThreshold
@@ -476,7 +502,7 @@ const MemberRatingInfoModal: FC<MemberRatingInfoModalProps> = (props: MemberRati
476502
)}
477503

478504
<div className={styles.axisLabels}>
479-
{chartAxisLabels.map((axisLabel: { label: string, value: number }) => (
505+
{visibleAxisLabels.map((axisLabel: { label: string, value: number }) => (
480506
<span
481507
key={axisLabel.label}
482508
style={{

0 commit comments

Comments
 (0)