Skip to content

Commit f976c5d

Browse files
authored
Merge pull request #860 from topcoder-platform/MP-316_social-links-modal-allow-edit
MP-316 - allow social links edit
2 parents e014c69 + 89f6d10 commit f976c5d

File tree

10 files changed

+388
-248
lines changed

10 files changed

+388
-248
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
@import '@libs/ui/styles/includes';
2+
3+
.linkItemWrap {
4+
display: flex;
5+
align-items: center;
6+
justify-content: flex-start;
7+
margin-bottom: $sp-2;
8+
9+
> svg {
10+
width: 24px;
11+
height: 24px;
12+
13+
path:not([stroke-linecap]) {
14+
fill: $black-100;
15+
}
16+
17+
}
18+
}
19+
20+
.linkItem {
21+
border-radius: 4px;
22+
padding: $sp-2 $sp-4;
23+
border: 1px solid $black-40;
24+
flex: 1;
25+
display: flex;
26+
align-items: center;
27+
justify-content: space-between;
28+
margin-left: $sp-4;
29+
30+
button {
31+
padding-right: 0;
32+
33+
&.button {
34+
margin-left: $sp-2;
35+
padding-left: $sp-2;
36+
padding-right: $sp-2;
37+
}
38+
39+
svg {
40+
width: 24px;
41+
height: 24px;
42+
}
43+
}
44+
}
45+
46+
.linkLabelWrap {
47+
display: flex;
48+
flex-direction: column;
49+
align-items: flex-start;
50+
51+
small {
52+
font-size: 11px;
53+
line-height: 11px;
54+
font-weight: $font-weight-medium;
55+
color: $turq-160;
56+
margin-bottom: $sp-1;
57+
}
58+
59+
p {
60+
word-break: break-all;
61+
}
62+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { FC, useState } from 'react'
2+
3+
import { Button, IconOutline } from '~/libs/ui'
4+
import { UserTrait } from '~/libs/core'
5+
6+
import { renderLinkIcon } from '../../MemberLinks'
7+
import { LinkForm, UserLink } from '../LinkForm'
8+
9+
import styles from './LinkEntry.module.scss'
10+
11+
interface LinkEntryProps {
12+
index: number
13+
link: UserTrait
14+
onSave: (link: UserTrait, index: number) => Promise<UserTrait | undefined>
15+
onRemove: (link: UserTrait) => void
16+
}
17+
18+
const LinkEntry: FC<LinkEntryProps> = props => {
19+
const [isEditing, setIsEditing] = useState(!(props.link.name || props.link.url))
20+
const isNew = !props.link.name && !props.link.url
21+
22+
function handleReomveClick(): void {
23+
props.onRemove(props.link)
24+
}
25+
26+
async function handleOnSave(link: UserLink): Promise<void> {
27+
if (!await props.onSave(link, props.index)) {
28+
return
29+
}
30+
31+
setIsEditing(false)
32+
}
33+
34+
function toggleIsEditing(): void {
35+
setIsEditing(editMode => !editMode)
36+
}
37+
38+
return !isEditing ? (
39+
<div className={styles.linkItemWrap} key={`member-link-${props.link.name}`}>
40+
{renderLinkIcon(props.link.name)}
41+
<div className={styles.linkItem}>
42+
<div className={styles.linkLabelWrap}>
43+
<small>{props.link.name}</small>
44+
<p>{props.link.url}</p>
45+
</div>
46+
<div>
47+
<Button
48+
className={styles.button}
49+
size='lg'
50+
icon={IconOutline.PencilIcon}
51+
onClick={toggleIsEditing}
52+
/>
53+
<Button
54+
className={styles.button}
55+
size='lg'
56+
icon={IconOutline.TrashIcon}
57+
onClick={handleReomveClick}
58+
/>
59+
</div>
60+
</div>
61+
</div>
62+
) : (
63+
<LinkForm
64+
link={props.link as UserLink}
65+
onSave={handleOnSave}
66+
onDiscard={toggleIsEditing}
67+
isNew={isNew}
68+
/>
69+
)
70+
}
71+
72+
export default LinkEntry
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as LinkEntry } from './LinkEntry'
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
@import '@libs/ui/styles/includes';
2+
3+
.formError {
4+
color: $red-100;
5+
}
6+
7+
.formWrap {
8+
display: flex;
9+
flex-direction: column;
10+
margin-top: $sp-2;
11+
12+
@include ltelg {
13+
:global(.input-wrapper) {
14+
margin-bottom: $sp-2;
15+
}
16+
}
17+
}
18+
19+
.form {
20+
display: flex;
21+
align-items: center;
22+
justify-content: space-between;
23+
24+
&>div:nth-child(1) {
25+
margin-right: $sp-2;
26+
min-width: 150px;
27+
}
28+
29+
&>div:nth-child(2) {
30+
flex: 1;
31+
margin-right: $sp-2;
32+
}
33+
&>button {
34+
margin-bottom: $sp-45;
35+
&.button {
36+
margin-left: $sp-2;
37+
padding-left: $sp-2;
38+
padding-right: $sp-2;
39+
}
40+
}
41+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { trim } from 'lodash'
2+
import { FC, useEffect, useState } from 'react'
3+
import classNames from 'classnames'
4+
5+
import { Button, IconOutline, InputSelect, InputText } from '~/libs/ui'
6+
7+
import { linkTypes } from '../link-types.config'
8+
import { isValidURL } from '../../../../lib'
9+
10+
import styles from './LinkForm.module.scss'
11+
12+
export interface UserLink {
13+
name: string
14+
url: string
15+
}
16+
17+
interface LinkFormProps {
18+
isNew: boolean
19+
link?: UserLink
20+
onSave: (link: UserLink) => void
21+
onDiscard: () => void
22+
}
23+
24+
const LinkForm: FC<LinkFormProps> = props => {
25+
const [formErrors, setFormErrors] = useState<{ [key: string]: string }>({})
26+
const [selectedLinkType, setSelectedLinkType] = useState<string | undefined>()
27+
const [selectedLinkURL, setSelectedLinkURL] = useState<string | undefined>()
28+
29+
function handleSelectedLinkTypeChange(event: React.ChangeEvent<HTMLInputElement>): void {
30+
setSelectedLinkType(event.target.value)
31+
}
32+
33+
function handleURLChange(event: React.ChangeEvent<HTMLInputElement>): void {
34+
setSelectedLinkURL(event.target.value)
35+
}
36+
37+
function handleFormAction(): void {
38+
setFormErrors({})
39+
40+
if (!selectedLinkType) {
41+
setFormErrors({ selectedLinkType: 'Please select a link type' })
42+
return
43+
}
44+
45+
if (!trim(selectedLinkURL)) {
46+
setFormErrors({ url: 'Please enter a URL' })
47+
return
48+
}
49+
50+
if (!isValidURL(selectedLinkURL as string)) {
51+
setFormErrors({ url: 'Invalid URL' })
52+
return
53+
}
54+
55+
props.onSave({
56+
name: selectedLinkType,
57+
url: trim(selectedLinkURL) || '',
58+
})
59+
}
60+
61+
function handleDiscardClick(): void {
62+
setFormErrors({})
63+
props.onDiscard()
64+
65+
if (!props.link) {
66+
return
67+
}
68+
69+
if (selectedLinkType !== props.link.name) {
70+
setSelectedLinkType(props.link.name)
71+
}
72+
73+
if (selectedLinkURL !== props.link.url) {
74+
setSelectedLinkURL(props.link.url)
75+
}
76+
}
77+
78+
useEffect(() => {
79+
if (!props.link) {
80+
return
81+
}
82+
83+
if (selectedLinkType !== props.link.name) {
84+
setSelectedLinkType(props.link.name)
85+
}
86+
87+
if (selectedLinkURL !== props.link.url) {
88+
setSelectedLinkURL(props.link.url)
89+
}
90+
91+
}, [props.link?.name, props.link?.url])
92+
93+
return (
94+
<form className={classNames(styles.formWrap)}>
95+
<div className={styles.form}>
96+
<InputSelect
97+
options={linkTypes}
98+
value={selectedLinkType}
99+
onChange={handleSelectedLinkTypeChange}
100+
name='linkType'
101+
label='Type'
102+
error={formErrors.selectedLinkType}
103+
placeholder='Select a link type'
104+
dirty
105+
/>
106+
107+
<InputText
108+
name='url'
109+
label='URL'
110+
error={formErrors.url}
111+
placeholder='Enter a URL'
112+
dirty
113+
tabIndex={-1}
114+
type='text'
115+
onChange={handleURLChange}
116+
value={selectedLinkURL}
117+
/>
118+
<Button
119+
className={styles.button}
120+
size='lg'
121+
icon={IconOutline.CheckIcon}
122+
onClick={handleFormAction}
123+
/>
124+
{!props.isNew && (
125+
<Button
126+
className={styles.button}
127+
size='lg'
128+
icon={IconOutline.XIcon}
129+
onClick={handleDiscardClick}
130+
/>
131+
)}
132+
</div>
133+
</form>
134+
)
135+
}
136+
137+
export default LinkForm
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as LinkForm, type UserLink } from './LinkForm'

0 commit comments

Comments
 (0)