Skip to content

Commit 9d3106e

Browse files
authored
Merge pull request #927 from topcoder-platform/TSJR-111
TSJR-111 grouped skill UI -> feature/standardized-skills
2 parents bba03bc + a617ee8 commit 9d3106e

File tree

38 files changed

+629
-270
lines changed

38 files changed

+629
-270
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"react-redux": "^8.0.4",
9090
"react-redux-toastr": "^7.6.10",
9191
"react-responsive": "^9.0.0-beta.5",
92+
"react-responsive-masonry": "^2.1.7",
9293
"react-responsive-modal": "^6.2.0",
9394
"react-router-dom": "^6.4.2",
9495
"react-scripts": "5.0.1",
@@ -159,6 +160,7 @@
159160
"@types/react-gtm-module": "^2.0.1",
160161
"@types/react-helmet": "^6.1.6",
161162
"@types/react-redux-toastr": "^7.6.2",
163+
"@types/react-responsive-masonry": "^2.1.1",
162164
"@types/react-router-dom": "^5.3.3",
163165
"@types/redux-actions": "2.6.2",
164166
"@types/redux-logger": "^3.0.9",

src/apps/onboarding/src/models/MemberInfo.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { MemberMaxRating } from '~/apps/talent-search/src/lib/models'
2-
import { MemberStats } from '~/libs/core'
3-
import { Skill } from '~/libs/shared'
2+
import { MemberStats, UserSkill } from '~/libs/core'
43

54
import MemberAddress from './MemberAddress'
65

@@ -14,7 +13,7 @@ export default interface MemberInfo {
1413
email: string
1514
accountAge: number
1615
maxRating: MemberMaxRating
17-
emsiSkills: Array<Skill>
16+
skills: Array<UserSkill>
1817
stats: Array<MemberStats>
1918
addresses?: MemberAddress[]
2019
country: string

src/apps/profiles/src/member-profile/MemberProfile.context.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { createContext, FC, ReactNode, useContext, useMemo } from 'react'
22

3-
import { Skill } from '~/libs/shared'
3+
import { UserSkill } from '~/libs/core'
44

55
export interface MemberProfileContextValue {
66
isTalentSearch?: boolean
77
skillsRenderer?: (
8-
skills: Pick<Skill, 'name'|'id'|'skillSources'>[]
8+
skills: Pick<UserSkill, 'name'|'id'|'levels'>[]
99
) => ReactNode
1010
}
1111

src/apps/profiles/src/member-profile/links/MemberLinks.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ export function renderLinkIcon(linkName: string): JSX.Element {
3232
return <GitHubLinkIcon />
3333
case 'Twitter':
3434
return <SocialIconTwitter />
35+
case 'X / Twitter':
36+
return <SocialIconTwitter />
3537
case 'LinkedIn':
3638
return <LinkedInLinkIcon />
3739
case 'Instagram':

src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkForm/LinkForm.tsx

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
import { trim } from 'lodash'
2-
import { FC, forwardRef, ForwardRefExoticComponent, SVGProps, useEffect, useImperativeHandle, useState } from 'react'
2+
import {
3+
FC,
4+
forwardRef,
5+
ForwardRefExoticComponent,
6+
SVGProps,
7+
useEffect,
8+
useImperativeHandle,
9+
useRef,
10+
useState,
11+
} from 'react'
312
import classNames from 'classnames'
413

514
import { Button, IconOutline, InputSelect, InputText } from '~/libs/ui'
615

7-
import { linkTypes } from '../link-types.config'
16+
import { additionalLinkTypes } from '../link-types.config'
817
import { isValidURL } from '../../../../lib'
918
import { renderLinkIcon } from '../../MemberLinks'
1019

@@ -24,7 +33,6 @@ interface LinkFormProps {
2433
onRemove?: () => void
2534
removeIcon?: FC<SVGProps<SVGSVGElement>>
2635
hideRemoveIcon?: boolean
27-
allowEmptyUrl?: boolean
2836
labelUrlField?: string
2937
disabled?: boolean
3038
}
@@ -41,6 +49,8 @@ const LinkForm: ForwardRefExoticComponent<
4149
const [selectedLinkType, setSelectedLinkType] = useState<string | undefined>()
4250
const [selectedLinkURL, setSelectedLinkURL] = useState<string | undefined>()
4351
const [shouldValidateForm, setShouldValidateForm] = useState<boolean>(false)
52+
const canShowTypeError = useRef(false)
53+
const canShowUrlError = useRef(false)
4454

4555
useEffect(() => {
4656
if (shouldValidateForm) {
@@ -52,37 +62,51 @@ const LinkForm: ForwardRefExoticComponent<
5262
resetForm() {
5363
setShouldValidateForm(false)
5464
setFormErrors({})
65+
canShowTypeError.current = false
66+
canShowUrlError.current = false
5567
},
5668
validateForm() {
69+
canShowTypeError.current = true
70+
canShowUrlError.current = true
5771
handleFormAction()
5872
},
5973
}))
6074

6175
function handleSelectedLinkTypeChange(event: React.ChangeEvent<HTMLInputElement>): void {
76+
canShowTypeError.current = true
6277
setSelectedLinkType(event.target.value)
6378
setShouldValidateForm(true)
6479
}
6580

6681
function handleURLChange(event: React.ChangeEvent<HTMLInputElement>): void {
82+
canShowUrlError.current = true
6783
setSelectedLinkURL(event.target.value)
6884
setShouldValidateForm(true)
6985
}
7086

71-
function handleFormAction(): void {
87+
function getFormError(): boolean {
7288
setFormErrors({})
7389

90+
let isError = false
7491
if (!selectedLinkType) {
75-
setFormErrors({ selectedLinkType: 'Please select a link type' })
76-
return
92+
isError = true
93+
if (canShowTypeError.current) {
94+
setFormErrors({ selectedLinkType: 'Please select a link type' })
95+
}
7796
}
7897

79-
if (!props.allowEmptyUrl && !trim(selectedLinkURL)) {
80-
setFormErrors({ url: 'Please enter a URL' })
81-
return
98+
if (selectedLinkURL && trim(selectedLinkURL) && !isValidURL(selectedLinkURL as string)) {
99+
isError = true
100+
if (canShowUrlError.current) {
101+
setFormErrors({ url: 'Invalid URL' })
102+
}
82103
}
83104

84-
if (selectedLinkURL && !isValidURL(selectedLinkURL as string)) {
85-
setFormErrors({ url: 'Invalid URL' })
105+
return isError
106+
}
107+
108+
function handleFormAction(): void {
109+
if (getFormError()) {
86110
return
87111
}
88112

@@ -91,14 +115,14 @@ const LinkForm: ForwardRefExoticComponent<
91115
if (absoluteURL.indexOf('://') > 0 || absoluteURL.indexOf('//') === 0) {
92116

93117
props.onSave({
94-
name: selectedLinkType,
118+
name: selectedLinkType ?? '',
95119
url: absoluteURL,
96120
})
97121
} else {
98122
absoluteURL = absoluteURL ? `https://${absoluteURL}` : ''
99123

100124
props.onSave({
101-
name: selectedLinkType,
125+
name: selectedLinkType ?? '',
102126
url: absoluteURL,
103127
})
104128
}
@@ -124,7 +148,7 @@ const LinkForm: ForwardRefExoticComponent<
124148
<div className={styles.form}>
125149
{props.allowEditType ? (
126150
<InputSelect
127-
options={linkTypes}
151+
options={additionalLinkTypes}
128152
value={selectedLinkType}
129153
onChange={handleSelectedLinkTypeChange}
130154
name='linkType'

src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/ModifyMemberLinksModal.tsx

Lines changed: 37 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { find, findIndex, omit, reject, uniqBy } from 'lodash'
2-
import { FC, useEffect, useRef, useState } from 'react'
1+
import { cloneDeep, findIndex, isEqual, omit, reject, uniqBy } from 'lodash'
2+
import { FC, useEffect, useMemo, useRef, useState } from 'react'
33
import { toast } from 'react-toastify'
44
import classNames from 'classnames'
55

@@ -28,7 +28,6 @@ const ModifyMemberLinksModal: FC<ModifyMemberLinksModalProps> = (props: ModifyMe
2828
const inputRef = useRef<HTMLInputElement | any>()
2929

3030
const [isSaving, setIsSaving] = useState<boolean>(false)
31-
const [hasChanges, setHasChanges] = useState<boolean>(false)
3231
const [currentMemberLinks, setCurrentMemberLinks] = useState<UserTrait[]>(
3332
[],
3433
)
@@ -44,16 +43,33 @@ const ModifyMemberLinksModal: FC<ModifyMemberLinksModalProps> = (props: ModifyMe
4443
name: 'Instagram',
4544
url: '',
4645
})
47-
const [newLink, setNewLink] = useState<UserTrait>({
46+
const [defaultLink, setDefaultLink] = useState<UserTrait>({
4847
name: '',
4948
url: '',
5049
})
5150

51+
const updatedLinks = useMemo(() => uniqBy(
52+
[
53+
defaultLinkedIn,
54+
defaultGitHub,
55+
defaultInstagram,
56+
defaultLink,
57+
...currentMemberLinks,
58+
].filter(
59+
l => l.name && l.url,
60+
),
61+
e => `${e.name}-${e.url}`,
62+
)
63+
.map(
64+
item => omit(item, ['id']),
65+
), [defaultLinkedIn, defaultGitHub, defaultInstagram, defaultLink, currentMemberLinks])
66+
const hasChanges = useMemo(() => !isEqual(updatedLinks, props.memberLinks), [updatedLinks])
67+
5268
const addNewLinkRef = useRef<LinkFormHandle>(null)
5369

5470
useEffect(() => {
5571
const memberLinks = [
56-
...(props.memberLinks ?? []),
72+
...cloneDeep(props.memberLinks ?? []),
5773
]
5874
const firstLinkedInIndex = findIndex(memberLinks, {
5975
name: 'LinkedIn',
@@ -76,6 +92,10 @@ const ModifyMemberLinksModal: FC<ModifyMemberLinksModalProps> = (props: ModifyMe
7692
setDefaultInstagram(memberLinks.splice(firstInstagramIndex, 1)[0])
7793
}
7894

95+
if (memberLinks.length > 0) {
96+
setDefaultLink(memberLinks.splice(0, 1)[0])
97+
}
98+
7999
setCurrentMemberLinks(memberLinks.map((item: UserTrait, index: number) => ({
80100
...item,
81101
id: `id-${index}-${(new Date())
@@ -85,30 +105,16 @@ const ModifyMemberLinksModal: FC<ModifyMemberLinksModalProps> = (props: ModifyMe
85105
}, [props.memberLinks])
86106

87107
function handleAddAdditional(): void {
88-
if (newLink.url && newLink.name) {
89-
const updatedLinks: UserTrait[] = uniqBy([
90-
defaultLinkedIn,
91-
defaultGitHub,
92-
defaultInstagram,
93-
...currentMemberLinks,
94-
].filter(l => l.name && l.url), e => `${e.name}-${e.url}`)
95-
if (!find(updatedLinks, newLink)) {
96-
setCurrentMemberLinks(links => [...links, {
97-
...newLink,
98-
id: `id-${(new Date())
99-
.getTime()}`,
100-
}])
101-
}
102-
103-
addNewLinkRef.current?.resetForm()
104-
setNewLink({
105-
name: '',
106-
url: '',
107-
})
108-
setHasChanges(true)
109-
} else {
110-
addNewLinkRef.current?.validateForm()
111-
}
108+
setCurrentMemberLinks(links => [...links, {
109+
id: `id-${(new Date())
110+
.getTime()}`,
111+
...defaultLink,
112+
}])
113+
setDefaultLink({
114+
name: '',
115+
url: '',
116+
})
117+
addNewLinkRef.current?.resetForm()
112118
}
113119

114120
function handleRemoveLink(index: number): void {
@@ -118,15 +124,12 @@ const ModifyMemberLinksModal: FC<ModifyMemberLinksModalProps> = (props: ModifyMe
118124
...currentMemberLinks,
119125
],
120126
)
121-
setHasChanges(true)
122127
}
123128

124129
function handleSaveLink(link: UserTrait, index: number): void {
125130
setCurrentMemberLinks(links => (links ?? []).map((l, i) => (
126131
i === index ? link : l
127132
)))
128-
129-
setHasChanges(true)
130133
}
131134

132135
function handleLinksSave(): void {
@@ -135,21 +138,6 @@ const ModifyMemberLinksModal: FC<ModifyMemberLinksModalProps> = (props: ModifyMe
135138
const updatedPersonalizationTraits: UserTrait[]
136139
= reject(props.memberPersonalizationTraitsFullData, (trait: UserTrait) => trait.links)
137140

138-
const updatedLinks: UserTrait[] = uniqBy(
139-
[
140-
defaultLinkedIn,
141-
defaultGitHub,
142-
defaultInstagram,
143-
...currentMemberLinks,
144-
].filter(
145-
l => l.name && l.url,
146-
),
147-
e => `${e.name}-${e.url}`,
148-
)
149-
.map(
150-
item => omit(item, ['id']),
151-
)
152-
153141
updateOrCreateMemberTraitsAsync(props.profile.handle, [{
154142
categoryName: UserTraitCategoryNames.personalization,
155143
traitId: UserTraitIds.personalization,
@@ -204,59 +192,50 @@ const ModifyMemberLinksModal: FC<ModifyMemberLinksModalProps> = (props: ModifyMe
204192
link={defaultLinkedIn as UserLink}
205193
onSave={function onSave(link: UserLink) {
206194
setDefaultLinkedIn(link)
207-
setHasChanges(true)
208195
}}
209196
onRemove={function onRemove() {
210197
setDefaultLinkedIn({
211198
...defaultLinkedIn,
212199
url: '',
213200
})
214-
setHasChanges(true)
215201
}}
216202
placeholder='Add URL'
217203
removeIcon={IconOutline.XCircleIcon}
218204
hideRemoveIcon={!defaultLinkedIn.url}
219-
allowEmptyUrl
220205
disabled={isSaving}
221206
labelUrlField='Linkedin'
222207
/>
223208
<LinkForm
224209
link={defaultGitHub as UserLink}
225210
onSave={function onSave(link: UserLink) {
226211
setDefaultGitHub(link)
227-
setHasChanges(true)
228212
}}
229213
onRemove={function onRemove() {
230214
setDefaultGitHub({
231215
...defaultGitHub,
232216
url: '',
233217
})
234-
setHasChanges(true)
235218
}}
236219
placeholder='Add URL'
237220
removeIcon={IconOutline.XCircleIcon}
238221
hideRemoveIcon={!defaultGitHub.url}
239-
allowEmptyUrl
240222
disabled={isSaving}
241223
labelUrlField='Git'
242224
/>
243225
<LinkForm
244226
link={defaultInstagram as UserLink}
245227
onSave={function onSave(link: UserLink) {
246228
setDefaultInstagram(link)
247-
setHasChanges(true)
248229
}}
249230
onRemove={function onRemove() {
250231
setDefaultInstagram({
251232
...defaultInstagram,
252233
url: '',
253234
})
254-
setHasChanges(true)
255235
}}
256236
placeholder='Add URL'
257237
removeIcon={IconOutline.XCircleIcon}
258238
hideRemoveIcon={!defaultInstagram.url}
259-
allowEmptyUrl
260239
disabled={isSaving}
261240
labelUrlField='Instagram'
262241
/>
@@ -265,8 +244,8 @@ const ModifyMemberLinksModal: FC<ModifyMemberLinksModalProps> = (props: ModifyMe
265244
<hr className={styles.spacer} />
266245

267246
<LinkForm
268-
link={newLink as UserLink}
269-
onSave={setNewLink}
247+
link={defaultLink as UserLink}
248+
onSave={setDefaultLink}
270249
allowEditType
271250
placeholder='http://'
272251
ref={addNewLinkRef}

0 commit comments

Comments
 (0)