Skip to content

Commit 3fa90fb

Browse files
authored
Merge pull request #828 from topcoder-platform/TAL-35_talent-search-updates
TAL-35 TAL-34 TAL-24 talent search updates -> dev
2 parents 0d1964c + 6a0253b commit 3fa90fb

File tree

12 files changed

+237
-149
lines changed

12 files changed

+237
-149
lines changed

src/apps/profiles/src/member-profile/profile-header/ProfileHeader.module.scss

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
padding-bottom: 150px;
1212
flex-direction: column;
1313
}
14-
1514
.photoWrap {
1615
position: relative;
1716
margin-right: 60px;
@@ -28,16 +27,6 @@
2827
height: 300px;
2928
border: 12px solid $tc-white;
3029
border-radius: 50%;
31-
background: linear-gradient(90deg, #075F96, #038664);
32-
display: flex;
33-
justify-content: center;
34-
align-items: center;
35-
36-
> span {
37-
font-size: 100px;
38-
line-height: 100px;
39-
font-family: $font-barlow;
40-
}
4130

4231
@include ltelg {
4332
width: 250px;
@@ -123,4 +112,4 @@
123112
margin-left: $sp-2;
124113
}
125114
}
126-
}
115+
}

src/apps/profiles/src/member-profile/profile-header/ProfileHeader.tsx

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
UserTraitIds,
1212
UserTraits,
1313
} from '~/libs/core'
14-
import { useCheckIsMobile } from '~/libs/shared'
14+
import { ProfilePicture, useCheckIsMobile } from '~/libs/shared'
1515

1616
import { AddButton, EditMemberPropertyBtn } from '../../components'
1717
import { EDIT_MODE_QUERY_PARAM, profileEditModes } from '../../config'
@@ -32,7 +32,6 @@ export type NamesAndHandleAppearance = 'namesOnly' | 'handleOnly' | 'namesAndHan
3232
const ProfileHeader: FC<ProfileHeaderProps> = (props: ProfileHeaderProps) => {
3333
const isMobile: boolean = useCheckIsMobile()
3434

35-
const photoURL: string | undefined = props.profile.photoURL
3635
const hasProfilePicture = !!props.profile.photoURL
3736

3837
const canEdit: boolean = props.authProfile?.handle === props.profile.handle
@@ -127,18 +126,7 @@ const ProfileHeader: FC<ProfileHeaderProps> = (props: ProfileHeaderProps) => {
127126
function renderMemberPhotoWrap(): JSX.Element {
128127
return (
129128
<div className={styles.photoWrap}>
130-
{
131-
photoURL ? (
132-
<img src={photoURL} alt='Topcoder - Member Profile Avatar' className={styles.profilePhoto} />
133-
) : (
134-
<div className={styles.profilePhoto}>
135-
<span className={styles.initials}>
136-
{props.profile.firstName[0]}
137-
{props.profile.lastName[0]}
138-
</span>
139-
</div>
140-
)
141-
}
129+
<ProfilePicture member={props.profile} className={styles.profilePhoto} />
142130
{canEdit && hasProfilePicture && (
143131
<EditMemberPropertyBtn
144132
className={styles.button}

src/apps/talent-search/src/components/search-input/SearchInput.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FC, useMemo } from 'react'
1+
import { FC, Ref, useMemo } from 'react'
22
import classNames from 'classnames'
33

44
import { IconOutline, InputMultiselectOption } from '~/libs/ui'
@@ -12,6 +12,7 @@ interface SearchInputProps {
1212
onChange: (skills: Skill[]) => void
1313
skills: Skill[]
1414
onSearch?: () => void
15+
inputRef?: Ref<any>
1516
}
1617

1718
const SearchInput: FC<SearchInputProps> = props => {
@@ -49,6 +50,7 @@ const SearchInput: FC<SearchInputProps> = props => {
4950
value={emsiSkills}
5051
onChange={onChange}
5152
onSubmit={props.onSearch}
53+
inputRef={props.inputRef}
5254
/>
5355
)
5456
}
Lines changed: 43 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
@import "@libs/ui/styles/includes";
22

33
.wrap {
4-
display: flex;
5-
gap: $sp-10;
6-
74
padding: $sp-55 $sp-8;
85
border-radius: $sp-4;
96

@@ -13,77 +10,30 @@
1310
color: $black-100;
1411
}
1512

16-
.profilePic {
17-
align-self: flex-start;
18-
flex: 0 0 36%;
13+
.topWrap {
14+
display: flex;
15+
gap: $sp-10;
16+
}
1917

18+
.profilePic {
2019
width: 200px;
2120
max-width: 200px;
22-
aspect-ratio: 1 / 1;
23-
border-radius: 50%;
24-
overflow: hidden;
25-
26-
display: flex;
27-
align-items: center;
28-
justify-content: center;
29-
30-
position: relative;
31-
z-index: 1;
32-
background: linear-gradient(90deg, #075f96, #038664);
33-
34-
&::before {
35-
content: "";
36-
display: block;
37-
position: absolute;
38-
top: -10%;
39-
left: -10%;
40-
z-index: -1;
41-
width: 120%;
42-
height: 120%;
43-
background-image: var(--background-image-url);
44-
background-position: center;
45-
background-size: cover;
46-
filter: blur(9px);
47-
}
48-
49-
.profileInitials {
50-
position: absolute;
51-
display: flex;
52-
align-items: center;
53-
justify-content: center;
54-
top: 0;
55-
left: 0;
56-
width: 100%;
57-
height: 100%;
58-
z-index: -1;
59-
60-
font-family: $font-barlow;
61-
font-size: 72px;
62-
color: $tc-white;
63-
}
64-
65-
img {
66-
display: block;
67-
max-width: 100%;
68-
max-height: 100%;
69-
object-fit: cover;
70-
71-
aspect-ratio: 1 / 1;
72-
border-radius: 50%;
73-
}
7421
}
7522

7623
.detailsContainer {
7724
flex: 1 1 auto;
7825

79-
padding: $sp-5 $sp-45 $sp-3 0;
26+
padding: $sp-5 $sp-45 $sp-45 0;
27+
display: flex;
28+
flex-direction: column;
8029
}
8130

8231
.talentInfo {
8332
display: flex;
8433
flex-direction: column;
8534
align-items: flex-start;
8635
gap: $sp-3;
36+
margin-bottom: $sp-45;
8737

8838
&Name {
8939
font-size: 30px;
@@ -107,13 +57,44 @@
10757

10858
.matchBar {
10959
max-width: 330px;
110-
margin-top: $sp-8;
60+
margin-top: auto;
61+
}
62+
63+
.skillsContainer {
64+
margin-top: $sp-5;
65+
border-top: 1px solid $black-10;
66+
padding-top: $sp-5;
67+
display: flex;
68+
flex-direction: column;
69+
gap: $sp-2;
70+
position: relative;
71+
72+
&Title {
73+
position: absolute;
74+
top: 0;
75+
left: 50%;
76+
background: $tc-white;
77+
transform: translate(-50%, -50%);
78+
padding: $sp-2 $sp-4;
79+
color: $black-60;
80+
}
81+
82+
&:global(.overline) {
83+
text-transform: uppercase;
84+
}
85+
}
86+
87+
.unmatchedSkills {
88+
display: flex;
89+
90+
color: $black-100;
91+
padding: 0 $sp-2;
92+
font-size: 14px;
11193
}
11294

11395
.skillsWrap {
11496
display: flex;
11597
align-items: center;
116-
gap: $sp-2;
98+
gap: $sp-1;
11799
flex-wrap: wrap;
118-
margin-top: $sp-5;
119100
}

src/apps/talent-search/src/components/talent-card/TalentCard.tsx

Lines changed: 79 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { CSSProperties, FC } from 'react'
2-
import { orderBy } from 'lodash'
1+
import { FC } from 'react'
32
import { Link } from 'react-router-dom'
3+
import { orderBy } from 'lodash'
4+
import classNames from 'classnames'
45
import codes from 'country-calling-code'
56

67
import { IconSolid } from '~/libs/ui'
7-
import { EmsiSkill, isSkillVerified } from '~/libs/shared'
8+
import { EmsiSkill, isSkillVerified, ProfilePicture } from '~/libs/shared'
89

910
import { MatchBar } from '../match-bar'
1011
import { SkillPill } from '../skill-pill'
@@ -31,66 +32,91 @@ interface TalentCardProps {
3132
const TalentCard: FC<TalentCardProps> = props => {
3233
const talentRoute = `${TALENT_SEARCH_PATHS.talent}/${props.member.handle}`
3334

34-
const visibleSkills = orderBy(
35+
const matchedSkills = orderBy(
3536
props.member.emsiSkills,
36-
[props.isMatchingSkill, isSkillVerified],
37-
['desc', 'desc'],
37+
isSkillVerified,
38+
'desc',
3839
)
39-
.slice(0, 6) as EmsiSkill[]
40+
.filter(props.isMatchingSkill)
4041

41-
const hiddenSkills = props.member.emsiSkills.length - visibleSkills.length
42+
const limitMatchedSkills = matchedSkills.slice(0, 10)
43+
44+
const provenSkills = limitMatchedSkills.filter(isSkillVerified)
45+
const selfSkills = limitMatchedSkills.filter(s => !isSkillVerified(s))
46+
const restSkills = matchedSkills.length - limitMatchedSkills.length
47+
48+
const restLabel = restSkills > 0 && (
49+
<div className={styles.unmatchedSkills}>
50+
{`+${restSkills} more skill${restSkills > 1 ? 's' : ''}`}
51+
</div>
52+
)
4253

4354
return (
4455
<Link to={talentRoute} className={styles.wrap}>
45-
<div
46-
className={styles.profilePic}
47-
style={{ '--background-image-url': `url(${props.member.photoURL})` } as CSSProperties}
48-
>
49-
<span className={styles.profileInitials}>
50-
{props.member.firstName.slice(0, 1)}
51-
{props.member.lastName.slice(0, 1)}
52-
</span>
53-
<img src={props.member.photoURL} alt='' />
54-
</div>
55-
<div className={styles.detailsContainer}>
56-
<div className={styles.talentInfo}>
57-
<div className={styles.talentInfoName}>
58-
{props.member.firstName}
59-
{' '}
60-
{props.member.lastName}
61-
</div>
62-
<div className={styles.talentInfoHandle}>
63-
<span className='body-medium-normal'>
64-
{props.member.handle}
65-
</span>
66-
</div>
67-
{props.member.addresses?.map(addr => (
68-
<div
69-
className={styles.talentInfoLocation}
70-
key={`${addr.streetAddr1}${addr.zip}${addr.city}${addr.stateCode}`}
71-
>
72-
<IconSolid.LocationMarkerIcon className='icon-xxl' />
73-
<span className='body-main'>
74-
{getAddrString(addr.city, getCountry(props.member.homeCountryCode))}
56+
<div className={styles.topWrap}>
57+
<ProfilePicture member={props.member} className={styles.profilePic} />
58+
<div className={styles.detailsContainer}>
59+
<div className={styles.talentInfo}>
60+
<div className={styles.talentInfoName}>
61+
{props.member.firstName}
62+
{' '}
63+
{props.member.lastName}
64+
</div>
65+
<div className={styles.talentInfoHandle}>
66+
<span className='body-medium-normal'>
67+
{props.member.handle}
7568
</span>
7669
</div>
77-
))}
78-
</div>
79-
<MatchBar className={styles.matchBar} percent={props.match} />
80-
<div className={styles.skillsWrap}>
81-
{visibleSkills.map(skill => (
82-
<SkillPill
83-
key={skill.skillId}
84-
theme={props.isMatchingSkill(skill) ? 'verified' : 'dark'}
85-
skill={skill}
86-
verified={isSkillVerified(skill)}
87-
/>
88-
))}
89-
{hiddenSkills > 0 && (
90-
<SkillPill theme='etc' skill={{ name: `+${hiddenSkills}` }} />
91-
)}
70+
{props.member.addresses?.map(addr => (
71+
<div
72+
className={styles.talentInfoLocation}
73+
key={`${addr.streetAddr1}${addr.zip}${addr.city}${addr.stateCode}`}
74+
>
75+
<IconSolid.LocationMarkerIcon className='icon-xxl' />
76+
<span className='body-main'>
77+
{getAddrString(addr.city, getCountry(props.member.homeCountryCode))}
78+
</span>
79+
</div>
80+
))}
81+
</div>
82+
<MatchBar className={styles.matchBar} percent={props.match} />
9283
</div>
9384
</div>
85+
<div className={styles.skillsContainer}>
86+
<div className={classNames(styles.skillsContainerTitle, 'overline')}>Matched skills</div>
87+
{provenSkills.length > 0 && (
88+
<>
89+
<div className='overline'>Proven skills</div>
90+
<div className={styles.skillsWrap}>
91+
{provenSkills.map(skill => (
92+
<SkillPill
93+
key={skill.skillId}
94+
theme='dark'
95+
skill={skill}
96+
verified={isSkillVerified(skill)}
97+
/>
98+
))}
99+
{!selfSkills.length && restLabel}
100+
</div>
101+
</>
102+
)}
103+
{selfSkills.length > 0 && (
104+
<>
105+
<div className='overline'>Self-selected skills</div>
106+
<div className={styles.skillsWrap}>
107+
{selfSkills.map(skill => (
108+
<SkillPill
109+
key={skill.skillId}
110+
theme='dark'
111+
skill={skill}
112+
verified={isSkillVerified(skill)}
113+
/>
114+
))}
115+
{restLabel}
116+
</div>
117+
</>
118+
)}
119+
</div>
94120
</Link>
95121
)
96122
}

0 commit comments

Comments
 (0)