diff --git a/src/apps/profiles/src/components/AddButton/AddButton.module.scss b/src/apps/profiles/src/components/AddButton/AddButton.module.scss new file mode 100644 index 000000000..b4317caa5 --- /dev/null +++ b/src/apps/profiles/src/components/AddButton/AddButton.module.scss @@ -0,0 +1,19 @@ +@import "@libs/ui/styles/includes"; + +.wrap { + margin-top: $sp-2; + + &:global(.mt0) { + margin-top: 0; + } +} + +.addButton { + padding: 0 !important; + color: $black-100; + + svg { + width: 20px; + height: 20px; + } +} diff --git a/src/apps/profiles/src/components/AddButton/AddButton.tsx b/src/apps/profiles/src/components/AddButton/AddButton.tsx new file mode 100644 index 000000000..df940acc2 --- /dev/null +++ b/src/apps/profiles/src/components/AddButton/AddButton.tsx @@ -0,0 +1,29 @@ +import { FC } from 'react' +import classNames from 'classnames' + +import { Button, IconOutline } from '~/libs/ui' + +import styles from './AddButton.module.scss' + +interface AddButtonProps { + className?: string + label?: string + onClick: () => void + variant?: 'mt0' +} + +const AddButton: FC = props => ( +
+
+) + +export default AddButton diff --git a/src/apps/profiles/src/components/AddButton/index.ts b/src/apps/profiles/src/components/AddButton/index.ts new file mode 100644 index 000000000..f0733c522 --- /dev/null +++ b/src/apps/profiles/src/components/AddButton/index.ts @@ -0,0 +1 @@ +export { default as AddButton } from './AddButton' diff --git a/src/apps/profiles/src/components/EditMemberPropertyBtn/EditMemberPropertyBtn.tsx b/src/apps/profiles/src/components/EditMemberPropertyBtn/EditMemberPropertyBtn.tsx index 23268d405..4d702a862 100644 --- a/src/apps/profiles/src/components/EditMemberPropertyBtn/EditMemberPropertyBtn.tsx +++ b/src/apps/profiles/src/components/EditMemberPropertyBtn/EditMemberPropertyBtn.tsx @@ -1,10 +1,12 @@ import { FC } from 'react' +import classNames from 'classnames' import { Button, IconOutline } from '~/libs/ui' import styles from './EditMemberPropertyBtn.module.scss' interface EditMemberPropertyBtnProps { + className?: string onClick: () => void } @@ -12,7 +14,7 @@ const EditMemberPropertyBtn: FC = (props: EditMember + + {canEdit && ( + + )} + )}

{props.profile?.description}

diff --git a/src/apps/profiles/src/member-profile/education-and-certifications/EducationAndCertifications.tsx b/src/apps/profiles/src/member-profile/education-and-certifications/EducationAndCertifications.tsx index 125d2b22f..fac21ca00 100644 --- a/src/apps/profiles/src/member-profile/education-and-certifications/EducationAndCertifications.tsx +++ b/src/apps/profiles/src/member-profile/education-and-certifications/EducationAndCertifications.tsx @@ -1,10 +1,18 @@ import { Dispatch, FC, SetStateAction, useEffect, useMemo, useState } from 'react' import { useSearchParams } from 'react-router-dom' -import { MemberTraitsAPI, useMemberTraits, UserProfile, UserTrait, UserTraitIds } from '~/libs/core' +import { + MemberTraitsAPI, + useMemberTraits, + UserCompletedCertificationsResponse, + UserProfile, + UserTrait, + UserTraitIds, + useUserCompletedCertifications, +} from '~/libs/core' import { EDIT_MODE_QUERY_PARAM, profileEditModes } from '../../config' -import { EditMemberPropertyBtn, EmptySection } from '../../components' +import { AddButton, EditMemberPropertyBtn, EmptySection } from '../../components' import { MemberTCAInfo } from '../tca-info' import { notifyUniNavi } from '../../lib' @@ -21,17 +29,28 @@ const EducationAndCertifications: FC = (props: const [queryParams]: [URLSearchParams, any] = useSearchParams() const editMode: string | null = queryParams.get(EDIT_MODE_QUERY_PARAM) + const { data: memberTCA, loading: tcaDataLoading }: UserCompletedCertificationsResponse + = useUserCompletedCertifications(props.profile?.userId) + const canEdit: boolean = props.authProfile?.handle === props.profile.handle const [isEditMode, setIsEditMode]: [boolean, Dispatch>] = useState(false) - const { data: memberEducationTraits, mutate: mutateTraits, loading }: MemberTraitsAPI + const { data: memberEducationTraits, mutate: mutateTraits, loading: traitsLoading }: MemberTraitsAPI = useMemberTraits(props.profile.handle, { traitIds: UserTraitIds.education }) + const loading = tcaDataLoading || traitsLoading + const memberEducation: UserTrait[] | undefined = useMemo(() => memberEducationTraits?.[0]?.traits?.data, [memberEducationTraits]) + const hasEducationData = useMemo(() => !!( + memberTCA?.courses?.length + || memberTCA?.enrollments?.length + || memberEducation?.length + ), [memberEducation?.length, memberTCA?.courses?.length, memberTCA?.enrollments?.length]) + useEffect(() => { if (props.authProfile && editMode === profileEditModes.education) { setIsEditMode(true) @@ -59,13 +78,11 @@ const EducationAndCertifications: FC = (props:

Education and Certifications

- { - canEdit && ( - - ) - } + {canEdit && hasEducationData && ( + + )}
@@ -81,9 +98,9 @@ const EducationAndCertifications: FC = (props: )}
- + - {!loading && !memberEducation?.length && ( + {!loading && !hasEducationData && ( = (props: )} + {!loading && canEdit && !hasEducationData && ( + + )} + { isEditMode && ( = (props: MemberLanguagesProps) const [isEditMode, setIsEditMode]: [boolean, Dispatch>] = useState(false) - const { data: memberLanguageTraits, mutate: mutateTraits }: { - data: UserTraits[] | undefined, - mutate: KeyedMutator, - } + const { data: memberLanguageTraits, mutate: mutateTraits, loading }: MemberTraitsAPI = useMemberTraits(props.profile.handle, { traitIds: UserTraitIds.languages }) const memberLanguages: UserTrait[] | undefined @@ -58,19 +54,24 @@ const MemberLanguages: FC = (props: MemberLanguagesProps) }, 1000) } - return canEdit || memberLanguages ? ( + return !loading && (canEdit || memberLanguages) ? (
-

Languages:

- { - canEdit && ( - - ) - } +

Languages

+ {canEdit && !!memberLanguages?.length && ( + + )}
+ {canEdit && !memberLanguages?.length && ( + + )}
{ memberLanguages?.map((trait: UserTrait) => ( diff --git a/src/apps/profiles/src/member-profile/links/MemberLinks.tsx b/src/apps/profiles/src/member-profile/links/MemberLinks.tsx index 3d172ec57..2dfd226e8 100644 --- a/src/apps/profiles/src/member-profile/links/MemberLinks.tsx +++ b/src/apps/profiles/src/member-profile/links/MemberLinks.tsx @@ -1,8 +1,7 @@ import { Dispatch, FC, SetStateAction, useEffect, useMemo, useState } from 'react' -import { KeyedMutator } from 'swr' import { useSearchParams } from 'react-router-dom' -import { useMemberTraits, UserProfile, UserTrait, UserTraitIds, UserTraits } from '~/libs/core' +import { MemberTraitsAPI, useMemberTraits, UserProfile, UserTrait, UserTraitIds } from '~/libs/core' import { IconOutline, SocialIconFacebook, @@ -10,7 +9,7 @@ import { SocialIconYoutube, } from '~/libs/ui' -import { EditMemberPropertyBtn } from '../../components' +import { AddButton, EditMemberPropertyBtn } from '../../components' import { EDIT_MODE_QUERY_PARAM, profileEditModes } from '../../config' import { notifyUniNavi } from '../../lib' @@ -53,10 +52,7 @@ const MemberLinks: FC = (props: MemberLinksProps) => { const [isEditMode, setIsEditMode]: [boolean, Dispatch>] = useState(false) - const { data: memberPersonalizationTraits, mutate: mutateTraits }: { - data: UserTraits[] | undefined, - mutate: KeyedMutator, - } + const { data: memberPersonalizationTraits, mutate: mutateTraits, loading }: MemberTraitsAPI = useMemberTraits(props.profile.handle, { traitIds: UserTraitIds.personalization }) const memberLinks: UserTrait | undefined @@ -71,7 +67,7 @@ const MemberLinks: FC = (props: MemberLinksProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.authProfile]) - function handleEditLangaguesClick(): void { + function handleEditClick(): void { setIsEditMode(true) } @@ -87,17 +83,15 @@ const MemberLinks: FC = (props: MemberLinksProps) => { }, 1000) } - return canEdit || memberLinks?.links ? ( + return !loading && (canEdit || memberLinks?.links) ? (
-

Links:

- { - canEdit && ( - - ) - } +

Links

+ {canEdit && !!memberLinks?.links.length && ( + + )}
@@ -115,6 +109,14 @@ const MemberLinks: FC = (props: MemberLinksProps) => { }
+ {canEdit && !memberLinks?.links.length && ( + + )} + { isEditMode && ( = (props: ProfileHeaderProps) => { const photoURL: string = props.profile.photoURL || DEFAULT_MEMBER_AVATAR + const hasProfilePicture = !!props.profile.photoURL const canEdit: boolean = props.authProfile?.handle === props.profile.handle @@ -111,13 +112,19 @@ const ProfileHeader: FC = (props: ProfileHeaderProps) => {
Topcoder - Member Profile Avatar - { - canEdit && ( - - ) - } + {canEdit && hasProfilePicture && ( + + )} + {canEdit && !hasProfilePicture && ( + + )}
{!traitsLoading && ( diff --git a/src/apps/profiles/src/member-profile/skills/MemberSkillsInfo.module.scss b/src/apps/profiles/src/member-profile/skills/MemberSkillsInfo.module.scss index d6586f5bf..3a2bccf43 100644 --- a/src/apps/profiles/src/member-profile/skills/MemberSkillsInfo.module.scss +++ b/src/apps/profiles/src/member-profile/skills/MemberSkillsInfo.module.scss @@ -12,7 +12,6 @@ .headerWrap { display: flex; align-items: center; - margin-bottom: $sp-4; h3 { font-family: $font-barlow; @@ -35,7 +34,6 @@ flex-wrap: wrap; column-gap: $sp-1; row-gap: $sp-2; - margin-top: $sp-2; .skillItem { border: 1px solid $black-20; @@ -68,4 +66,4 @@ text-decoration: underline; } } -} \ No newline at end of file +} diff --git a/src/apps/profiles/src/member-profile/skills/MemberSkillsInfo.tsx b/src/apps/profiles/src/member-profile/skills/MemberSkillsInfo.tsx index 1c29239ca..a59e624cb 100644 --- a/src/apps/profiles/src/member-profile/skills/MemberSkillsInfo.tsx +++ b/src/apps/profiles/src/member-profile/skills/MemberSkillsInfo.tsx @@ -5,7 +5,7 @@ import classNames from 'classnames' import { isVerifiedSkill, UserEMSISkill, UserProfile } from '~/libs/core' import { IconOutline } from '~/libs/ui' -import { EditMemberPropertyBtn, EmptySection } from '../../components' +import { AddButton, EditMemberPropertyBtn, EmptySection } from '../../components' import { EDIT_MODE_QUERY_PARAM, profileEditModes } from '../../config' import { ModifySkillsModal } from './ModifySkillsModal' @@ -61,7 +61,7 @@ const MemberSkillsInfo: FC = (props: MemberSkillsInfoProp

Skills

{ - canEdit && ( + canEdit && memberEMSISkills?.length > 0 && ( @@ -94,7 +94,7 @@ const MemberSkillsInfo: FC = (props: MemberSkillsInfoProp This member has not yet provided skills, but check back soon as their skills will grow as @@ -102,6 +102,12 @@ const MemberSkillsInfo: FC = (props: MemberSkillsInfoProp )}
+ {canEdit && !memberEMSISkills?.length && ( + + )} { isEditMode && ( diff --git a/src/apps/profiles/src/member-profile/tca-info/MemberTCAInfo.tsx b/src/apps/profiles/src/member-profile/tca-info/MemberTCAInfo.tsx index 62436d15c..041600ae6 100644 --- a/src/apps/profiles/src/member-profile/tca-info/MemberTCAInfo.tsx +++ b/src/apps/profiles/src/member-profile/tca-info/MemberTCAInfo.tsx @@ -8,18 +8,17 @@ import { LearnUserCertificationProgress, TCACertificationEnrollmentBase, } from '~/apps/learn/src/lib' -import { UserCompletedCertificationsData, UserProfile, useUserCompletedCertifications } from '~/libs/core' +import { UserCompletedCertificationsData, UserProfile } from '~/libs/core' import { BaseModal, TCALogo } from '~/libs/ui' import styles from './MemberTCAInfo.module.scss' interface MemberTCAInfoProps { + memberTcaData?: UserCompletedCertificationsData profile: UserProfile | undefined } const MemberTCAInfo: React.FC = (props: MemberTCAInfoProps) => { - const { data: memberTCA }: { data: UserCompletedCertificationsData | undefined } - = useUserCompletedCertifications(props.profile?.userId) const [certPreviewModalIsOpen, setCertPreviewModalIsOpen]: [boolean, Dispatch>] = useState(false) @@ -72,7 +71,7 @@ const MemberTCAInfo: React.FC = (props: MemberTCAInfoProps) window.open(url, '_blank') } - return memberTCA && (memberTCA.courses.length || memberTCA.enrollments.length) ? ( + return props.memberTcaData && (props.memberTcaData.courses.length || props.memberTcaData.enrollments.length) ? (
@@ -80,12 +79,12 @@ const MemberTCAInfo: React.FC = (props: MemberTCAInfoProps)
{ - memberTCA.enrollments.length ? ( + props.memberTcaData.enrollments.length ? ( <>

CERTIFICATIONS

{ - memberTCA.enrollments.map(enrollment => ( + props.memberTcaData.enrollments.map(enrollment => (
= (props: MemberTCAInfoProps)

COURSES

{ - memberTCA.courses.map(course => ( + props.memberTcaData.courses.map(course => (
= (props: WorkExpirenceProps) => {

Experience

- { - canEdit && ( - - ) - } + {canEdit && !!workExpirence?.length && ( + + )}
{!loading && ( - (workExpirence?.length as number) > 0 - ? workExpirence?.map((work: UserTrait) => ( - - )) - : ( - - I'm still building up my experience here at Topcoder. - - ) + <> + {(workExpirence?.length as number) > 0 + ? workExpirence?.map((work: UserTrait) => ( + + )) + : ( + + I'm still building up my experience here at Topcoder. + + )} + {canEdit && !workExpirence?.length && ( + + )} + )}
diff --git a/src/libs/core/lib/profile/data-providers/useUserCompletedCertifications.ts b/src/libs/core/lib/profile/data-providers/useUserCompletedCertifications.ts index 005e85e32..5238bd176 100644 --- a/src/libs/core/lib/profile/data-providers/useUserCompletedCertifications.ts +++ b/src/libs/core/lib/profile/data-providers/useUserCompletedCertifications.ts @@ -14,13 +14,15 @@ export interface UserCompletedCertificationsData { courses: ReadonlyArray } -export function useUserCompletedCertifications( - userId: number | undefined, -): { +export interface UserCompletedCertificationsResponse { data: UserCompletedCertificationsData | undefined loading: boolean ready: boolean -} { +} + +export function useUserCompletedCertifications( + userId: number | undefined, +): UserCompletedCertificationsResponse { const url: string = `${learnBaseURL()}/completed-certifications/${userId}` diff --git a/src/libs/shared/lib/components/member-skill-editor/use-member-skill-editor.tsx b/src/libs/shared/lib/components/member-skill-editor/use-member-skill-editor.tsx index 1a7972cc8..5ff00a6d6 100644 --- a/src/libs/shared/lib/components/member-skill-editor/use-member-skill-editor.tsx +++ b/src/libs/shared/lib/components/member-skill-editor/use-member-skill-editor.tsx @@ -53,7 +53,7 @@ export const useMemberSkillEditor = ({ return } - updateMemberEmsiSkills(profile.userId, emsiSkills) + await updateMemberEmsiSkills(profile.userId, emsiSkills) }, [isEmsiInitialized, profile?.userId, skills]) // Handle user changes diff --git a/src/libs/shared/lib/services/emsi-skills/emsi-skills.service.ts b/src/libs/shared/lib/services/emsi-skills/emsi-skills.service.ts index 835fcd9a3..954390907 100644 --- a/src/libs/shared/lib/services/emsi-skills/emsi-skills.service.ts +++ b/src/libs/shared/lib/services/emsi-skills/emsi-skills.service.ts @@ -1,5 +1,5 @@ import { EnvironmentConfig } from '~/config' -import { xhrGetAsync, xhrPostAsync, xhrPutAsync } from '~/libs/core' +import { xhrDeleteAsync, xhrGetAsync, xhrPostAsync, xhrPutAsync } from '~/libs/core' import { EmsiSkill, Skill } from './skill.model' @@ -23,3 +23,7 @@ export async function updateMemberEmsiSkills(userId: string | number, skills: Sk emsiSkills: skills, }) } + +export async function deleteMemberEmsiSkills(userId: string | number): Promise { + return xhrDeleteAsync(`${EnvironmentConfig.API.V5}/emsi-skills/member-emsi-skills/${userId}`) +}