diff --git a/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkEntry/LinkEntry.module.scss b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkEntry/LinkEntry.module.scss new file mode 100644 index 000000000..5a08836ad --- /dev/null +++ b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkEntry/LinkEntry.module.scss @@ -0,0 +1,62 @@ +@import '@libs/ui/styles/includes'; + +.linkItemWrap { + display: flex; + align-items: center; + justify-content: flex-start; + margin-bottom: $sp-2; + + > svg { + width: 24px; + height: 24px; + + path:not([stroke-linecap]) { + fill: $black-100; + } + + } +} + +.linkItem { + border-radius: 4px; + padding: $sp-2 $sp-4; + border: 1px solid $black-40; + flex: 1; + display: flex; + align-items: center; + justify-content: space-between; + margin-left: $sp-4; + + button { + padding-right: 0; + + &.button { + margin-left: $sp-2; + padding-left: $sp-2; + padding-right: $sp-2; + } + + svg { + width: 24px; + height: 24px; + } + } +} + +.linkLabelWrap { + display: flex; + flex-direction: column; + align-items: flex-start; + + small { + font-size: 11px; + line-height: 11px; + font-weight: $font-weight-medium; + color: $turq-160; + margin-bottom: $sp-1; + } + + p { + word-break: break-all; + } +} diff --git a/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkEntry/LinkEntry.tsx b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkEntry/LinkEntry.tsx new file mode 100644 index 000000000..2549347f6 --- /dev/null +++ b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkEntry/LinkEntry.tsx @@ -0,0 +1,72 @@ +import { FC, useState } from 'react' + +import { Button, IconOutline } from '~/libs/ui' +import { UserTrait } from '~/libs/core' + +import { renderLinkIcon } from '../../MemberLinks' +import { LinkForm, UserLink } from '../LinkForm' + +import styles from './LinkEntry.module.scss' + +interface LinkEntryProps { + index: number + link: UserTrait + onSave: (link: UserTrait, index: number) => Promise + onRemove: (link: UserTrait) => void +} + +const LinkEntry: FC = props => { + const [isEditing, setIsEditing] = useState(!(props.link.name || props.link.url)) + const isNew = !props.link.name && !props.link.url + + function handleReomveClick(): void { + props.onRemove(props.link) + } + + async function handleOnSave(link: UserLink): Promise { + if (!await props.onSave(link, props.index)) { + return + } + + setIsEditing(false) + } + + function toggleIsEditing(): void { + setIsEditing(editMode => !editMode) + } + + return !isEditing ? ( +
+ {renderLinkIcon(props.link.name)} +
+
+ {props.link.name} +

{props.link.url}

+
+
+
+
+
+ ) : ( + + ) +} + +export default LinkEntry diff --git a/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkEntry/index.ts b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkEntry/index.ts new file mode 100644 index 000000000..07c1b1292 --- /dev/null +++ b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkEntry/index.ts @@ -0,0 +1 @@ +export { default as LinkEntry } from './LinkEntry' diff --git a/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkForm/LinkForm.module.scss b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkForm/LinkForm.module.scss new file mode 100644 index 000000000..83187eaac --- /dev/null +++ b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkForm/LinkForm.module.scss @@ -0,0 +1,41 @@ +@import '@libs/ui/styles/includes'; + +.formError { + color: $red-100; +} + +.formWrap { + display: flex; + flex-direction: column; + margin-top: $sp-2; + + @include ltelg { + :global(.input-wrapper) { + margin-bottom: $sp-2; + } + } +} + +.form { + display: flex; + align-items: center; + justify-content: space-between; + + &>div:nth-child(1) { + margin-right: $sp-2; + min-width: 150px; + } + + &>div:nth-child(2) { + flex: 1; + margin-right: $sp-2; + } + &>button { + margin-bottom: $sp-45; + &.button { + margin-left: $sp-2; + padding-left: $sp-2; + padding-right: $sp-2; + } + } +} diff --git a/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkForm/LinkForm.tsx b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkForm/LinkForm.tsx new file mode 100644 index 000000000..6158c708d --- /dev/null +++ b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkForm/LinkForm.tsx @@ -0,0 +1,137 @@ +import { trim } from 'lodash' +import { FC, useEffect, useState } from 'react' +import classNames from 'classnames' + +import { Button, IconOutline, InputSelect, InputText } from '~/libs/ui' + +import { linkTypes } from '../link-types.config' +import { isValidURL } from '../../../../lib' + +import styles from './LinkForm.module.scss' + +export interface UserLink { + name: string + url: string +} + +interface LinkFormProps { + isNew: boolean + link?: UserLink + onSave: (link: UserLink) => void + onDiscard: () => void +} + +const LinkForm: FC = props => { + const [formErrors, setFormErrors] = useState<{ [key: string]: string }>({}) + const [selectedLinkType, setSelectedLinkType] = useState() + const [selectedLinkURL, setSelectedLinkURL] = useState() + + function handleSelectedLinkTypeChange(event: React.ChangeEvent): void { + setSelectedLinkType(event.target.value) + } + + function handleURLChange(event: React.ChangeEvent): void { + setSelectedLinkURL(event.target.value) + } + + function handleFormAction(): void { + setFormErrors({}) + + if (!selectedLinkType) { + setFormErrors({ selectedLinkType: 'Please select a link type' }) + return + } + + if (!trim(selectedLinkURL)) { + setFormErrors({ url: 'Please enter a URL' }) + return + } + + if (!isValidURL(selectedLinkURL as string)) { + setFormErrors({ url: 'Invalid URL' }) + return + } + + props.onSave({ + name: selectedLinkType, + url: trim(selectedLinkURL) || '', + }) + } + + function handleDiscardClick(): void { + setFormErrors({}) + props.onDiscard() + + if (!props.link) { + return + } + + if (selectedLinkType !== props.link.name) { + setSelectedLinkType(props.link.name) + } + + if (selectedLinkURL !== props.link.url) { + setSelectedLinkURL(props.link.url) + } + } + + useEffect(() => { + if (!props.link) { + return + } + + if (selectedLinkType !== props.link.name) { + setSelectedLinkType(props.link.name) + } + + if (selectedLinkURL !== props.link.url) { + setSelectedLinkURL(props.link.url) + } + + }, [props.link?.name, props.link?.url]) + + return ( +
+
+ + + +
+
+ ) +} + +export default LinkForm diff --git a/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkForm/index.ts b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkForm/index.ts new file mode 100644 index 000000000..d8dc8c4c4 --- /dev/null +++ b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/LinkForm/index.ts @@ -0,0 +1 @@ +export { default as LinkForm, type UserLink } from './LinkForm' diff --git a/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/ModifyMemberLinksModal.module.scss b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/ModifyMemberLinksModal.module.scss index 48b1ccbef..c7d7df524 100644 --- a/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/ModifyMemberLinksModal.module.scss +++ b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/ModifyMemberLinksModal.module.scss @@ -1,51 +1,18 @@ @import '@libs/ui/styles/includes'; +.memberLinksModalBody { + min-height: 300px; +} + .modalButtons { display: flex; justify-content: space-between; width: 100%; } -.formError { - color: $red-100; -} - -.formWrap { - display: flex; - flex-direction: column; - min-height: 300px; - margin-top: $sp-2; - - @include ltelg { - :global(.input-wrapper) { - margin-bottom: $sp-2; - } - } - - .form { - display: flex; - align-items: center; - justify-content: space-between; - - &>div:nth-child(1) { - margin-right: $sp-2; - min-width: 150px; - } - - &>div:nth-child(2) { - flex: 1; - } - } - - .formCTAs { - margin-top: $sp-2; - } -} - .links { margin-bottom: $sp-4; padding: $sp-4 0 $sp-2; - border-bottom: 1px solid $black-20; @include ltelg { display: flex; @@ -57,59 +24,8 @@ padding: 0; border: none; } +} - .linkItemWrap { - display: flex; - align-items: center; - justify-content: flex-start; - margin-bottom: $sp-2; - - >svg { - width: 24px; - height: 24px; - - path:not([stroke-linecap]) { - fill: $black-100; - } - - } - - .linkItem { - border-radius: 4px; - padding: $sp-2 $sp-4; - border: 1px solid $black-40; - flex: 1; - display: flex; - align-items: center; - justify-content: space-between; - margin-left: $sp-4; - - .linkLabelWrap { - display: flex; - flex-direction: column; - align-items: flex-start; - - small { - font-size: 11px; - line-height: 11px; - font-weight: $font-weight-medium; - color: $turq-160; - margin-bottom: $sp-1; - } - - p { - word-break: break-all; - } - } - - button { - padding-right: 0; - - svg { - width: 24px; - height: 24px; - } - } - } - } -} \ No newline at end of file +.formCTAs { + margin-top: $sp-2; +} diff --git a/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/ModifyMemberLinksModal.tsx b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/ModifyMemberLinksModal.tsx index c3b6aa869..6b0e82792 100644 --- a/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/ModifyMemberLinksModal.tsx +++ b/src/apps/profiles/src/member-profile/links/ModifyMemberLinksModal/ModifyMemberLinksModal.tsx @@ -1,9 +1,9 @@ -import { bind, reject, trim } from 'lodash' -import { Dispatch, FC, MutableRefObject, SetStateAction, useMemo, useRef, useState } from 'react' +import { reject } from 'lodash' +import { FC, useMemo, useRef, useState } from 'react' import { toast } from 'react-toastify' import classNames from 'classnames' -import { BaseModal, Button, IconOutline, InputSelect, InputText } from '~/libs/ui' +import { BaseModal, Button } from '~/libs/ui' import { updateOrCreateMemberTraitsAsync, UserProfile, @@ -12,10 +12,7 @@ import { UserTraitIds, } from '~/libs/core' -import { renderLinkIcon } from '../MemberLinks' -import { isValidURL } from '../../../lib' - -import { linkTypes } from './link-types.config' +import { LinkEntry } from './LinkEntry' import styles from './ModifyMemberLinksModal.module.scss' interface ModifyMemberLinksModalProps { @@ -27,49 +24,50 @@ interface ModifyMemberLinksModalProps { } const ModifyMemberLinksModal: FC = (props: ModifyMemberLinksModalProps) => { - const formElRef: MutableRefObject = useRef() - - const [isSaving, setIsSaving]: [boolean, Dispatch>] - = useState(false) + const inputRef = useRef() - const [isFormChanged, setIsFormChanged]: [boolean, Dispatch>] - = useState(false) + const [isSaving, setIsSaving] = useState(false) + const [hasChanges, setHasChanges] = useState(false) + const [currentMemberLinks, setCurrentMemberLinks] = useState( + props.memberLinks?.length ? props.memberLinks : [{}], + ) - const [formErrors, setFormErrors]: [ - { [key: string]: string }, - Dispatch> - ] - = useState<{ [key: string]: string }>({}) + const hasNewInput = useMemo(() => ( + !!currentMemberLinks?.find(d => (!d.name && !d.url)) + ), [currentMemberLinks]) - const [selectedLinkType, setSelectedLinkType]: [ - string | undefined, - Dispatch> - ] - = useState() + function handleAddAdditional(): void { + if (hasNewInput) { + return + } - const [currentMemberLinks, setCurrentMemberLinks]: [ - UserTrait[] | undefined, - Dispatch> - ] - = useState(props.memberLinks) + setCurrentMemberLinks(links => (links ?? []).concat({ name: '', url: '' })) + } - const linkTypesSelect: any = useMemo(() => linkTypes.map((link: any) => ({ - label: link.name, - value: link.name, - })), []) + function handleRemoveLink(trait: UserTrait): void { + setCurrentMemberLinks( + currentMemberLinks?.filter((item: UserTrait) => item.url !== trait.url), + ) + setHasChanges(true) + } - const [selectedLinkURL, setSelectedLinkURL]: [ - string | undefined, - Dispatch> - ] - = useState() + async function handleSaveLink(link: UserTrait, index: number): Promise { + const existingLinkItemIndex = currentMemberLinks?.findIndex((item: UserTrait) => ( + item.url?.toLowerCase() === link?.url?.toLowerCase() + )) ?? -1 + const isDuplicateLink = existingLinkItemIndex > -1 && existingLinkItemIndex !== index - function handleSelectedLinkTypeChange(event: React.ChangeEvent): void { - setSelectedLinkType(event.target.value) - const validURL = isValidURL(selectedLinkURL as string) - if (validURL) { - setIsFormChanged(true) + if (isDuplicateLink) { + toast.info('Link already exists', { position: toast.POSITION.BOTTOM_RIGHT }) + return undefined } + + setCurrentMemberLinks(links => (links ?? []).map((l, i) => ( + i === index ? link : l + ))) + + setHasChanges(true) + return link } function handleLinksSave(): void { @@ -80,14 +78,7 @@ const ModifyMemberLinksModal: FC = (props: ModifyMe const updatedLinks: UserTrait[] = [ ...(currentMemberLinks || []), - ] - - if (selectedLinkType && isValidURL(selectedLinkURL as string)) { - updatedLinks.push({ - name: selectedLinkType, - url: trim(selectedLinkURL) || '', - }) - } + ].filter(l => l.name && l.url) updateOrCreateMemberTraitsAsync(props.profile.handle, [{ categoryName: UserTraitCategoryNames.personalization, @@ -111,71 +102,14 @@ const ModifyMemberLinksModal: FC = (props: ModifyMe }) } - function handleFormAction(): void { - if (!selectedLinkType) { - setFormErrors({ selectedLinkType: 'Please select a link type' }) - return - } - - if (!trim(selectedLinkURL)) { - setFormErrors({ url: 'Please enter a URL' }) - return - } - - const validURL = isValidURL(selectedLinkURL as string) - if (!validURL) { - setFormErrors({ url: 'Invalid URL' }) - return - } - - if (currentMemberLinks?.find((item: UserTrait) => item.url.toLowerCase() === selectedLinkURL?.toLowerCase())) { - toast.info('Link already exists', { position: toast.POSITION.BOTTOM_RIGHT }) - resetForm() - return - } - - setCurrentMemberLinks([ - ...(currentMemberLinks || []), - { - name: selectedLinkType, - url: trim(selectedLinkURL) || '', - }, - ]) - - setIsFormChanged(true) - resetForm() - } - - function resetForm(): void { - formElRef.current.reset() - setSelectedLinkType(undefined) - setSelectedLinkURL(undefined) - setFormErrors({}) - } - - function handleRemoveLink(trait: UserTrait): void { - setCurrentMemberLinks( - currentMemberLinks?.filter((item: UserTrait) => item.url !== trait.url), - ) - setIsFormChanged(true) - } - - function handleURLChange(event: React.ChangeEvent): void { - setSelectedLinkURL(event.target.value) - - const validURL = isValidURL(event.target.value) - if (validURL && selectedLinkType) { - setIsFormChanged(true) - } - } - return (