From cb0e5d5e3a810577bcd1d2101d09bc10624e1450 Mon Sep 17 00:00:00 2001 From: Vasilica Date: Mon, 17 Jul 2023 10:00:54 +0300 Subject: [PATCH 1/3] use await to pass unhandled errors up in the stack --- .../components/member-skill-editor/use-member-skill-editor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 0b67972d4c830382ad821ee3511b5c70e23146b2 Mon Sep 17 00:00:00 2001 From: Vasilica Date: Mon, 17 Jul 2023 11:22:48 +0300 Subject: [PATCH 2/3] MP-172 - Update profile page when empty --- .../AddButton/AddButton.module.scss | 19 +++++++ .../src/components/AddButton/AddButton.tsx | 29 ++++++++++ .../src/components/AddButton/index.ts | 1 + .../EditMemberPropertyBtn.tsx | 4 +- src/apps/profiles/src/components/index.ts | 1 + .../about-me/AboutMe.module.scss | 7 ++- .../src/member-profile/about-me/AboutMe.tsx | 53 +++++++++++++------ .../EducationAndCertifications.tsx | 44 +++++++++++---- .../languages/MemberLanguages.tsx | 23 ++++---- .../src/member-profile/links/MemberLinks.tsx | 26 +++++---- .../profile-header/ProfileHeader.module.scss | 9 +++- .../profile-header/ProfileHeader.tsx | 23 +++++--- .../skills/MemberSkillsInfo.module.scss | 4 +- .../skills/MemberSkillsInfo.tsx | 12 +++-- .../member-profile/tca-info/MemberTCAInfo.tsx | 13 +++-- .../work-expirence/WorkExpirence.tsx | 52 +++++++++++------- .../emsi-skills/emsi-skills.service.ts | 6 ++- 17 files changed, 234 insertions(+), 92 deletions(-) create mode 100644 src/apps/profiles/src/components/AddButton/AddButton.module.scss create mode 100644 src/apps/profiles/src/components/AddButton/AddButton.tsx create mode 100644 src/apps/profiles/src/components/AddButton/index.ts 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..9213ec142 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, + UserCompletedCertificationsData, + 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,6 +29,9 @@ const EducationAndCertifications: FC = (props: const [queryParams]: [URLSearchParams, any] = useSearchParams() const editMode: string | null = queryParams.get(EDIT_MODE_QUERY_PARAM) + const { data: memberTCA }: { data: UserCompletedCertificationsData | undefined } + = useUserCompletedCertifications(props.profile?.userId) + const canEdit: boolean = props.authProfile?.handle === props.profile.handle const [isEditMode, setIsEditMode]: [boolean, Dispatch>] @@ -32,6 +43,12 @@ const EducationAndCertifications: FC = (props: 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 +76,11 @@ const EducationAndCertifications: FC = (props:

Education and Certifications

- { - canEdit && ( - - ) - } + {canEdit && hasEducationData && ( + + )}
@@ -81,9 +96,9 @@ const EducationAndCertifications: FC = (props: )}
- + - {!loading && !memberEducation?.length && ( + {!loading && !hasEducationData && ( = (props: )} + {canEdit && !hasEducationData && ( + + )} + { isEditMode && ( = (props: MemberLanguagesProps) return 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..c08520126 100644 --- a/src/apps/profiles/src/member-profile/links/MemberLinks.tsx +++ b/src/apps/profiles/src/member-profile/links/MemberLinks.tsx @@ -10,7 +10,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' @@ -71,7 +71,7 @@ const MemberLinks: FC = (props: MemberLinksProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.authProfile]) - function handleEditLangaguesClick(): void { + function handleEditClick(): void { setIsEditMode(true) } @@ -90,14 +90,12 @@ const MemberLinks: FC = (props: MemberLinksProps) => { return canEdit || memberLinks?.links ? (
-

Links:

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

Links

+ {canEdit && !!memberLinks?.links.length && ( + + )}
@@ -115,6 +113,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/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}`) +} From 25ab120a2bb7324e45469221c5e68b25b5ced69a Mon Sep 17 00:00:00 2001 From: Vasilica Date: Mon, 17 Jul 2023 11:53:01 +0300 Subject: [PATCH 3/3] MP-172 - prevent data flicker --- .../EducationAndCertifications.tsx | 10 ++++++---- .../src/member-profile/languages/MemberLanguages.tsx | 10 +++------- .../profiles/src/member-profile/links/MemberLinks.tsx | 10 +++------- .../data-providers/useUserCompletedCertifications.ts | 10 ++++++---- 4 files changed, 18 insertions(+), 22 deletions(-) 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 9213ec142..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 @@ -4,7 +4,7 @@ import { useSearchParams } from 'react-router-dom' import { MemberTraitsAPI, useMemberTraits, - UserCompletedCertificationsData, + UserCompletedCertificationsResponse, UserProfile, UserTrait, UserTraitIds, @@ -29,7 +29,7 @@ const EducationAndCertifications: FC = (props: const [queryParams]: [URLSearchParams, any] = useSearchParams() const editMode: string | null = queryParams.get(EDIT_MODE_QUERY_PARAM) - const { data: memberTCA }: { data: UserCompletedCertificationsData | undefined } + const { data: memberTCA, loading: tcaDataLoading }: UserCompletedCertificationsResponse = useUserCompletedCertifications(props.profile?.userId) const canEdit: boolean = props.authProfile?.handle === props.profile.handle @@ -37,9 +37,11 @@ const EducationAndCertifications: FC = (props: 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]) @@ -111,7 +113,7 @@ const EducationAndCertifications: FC = (props: )} - {canEdit && !hasEducationData && ( + {!loading && canEdit && !hasEducationData && ( = (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,7 +54,7 @@ const MemberLanguages: FC = (props: MemberLanguagesProps) }, 1000) } - return canEdit || memberLanguages ? ( + return !loading && (canEdit || memberLanguages) ? (

Languages

diff --git a/src/apps/profiles/src/member-profile/links/MemberLinks.tsx b/src/apps/profiles/src/member-profile/links/MemberLinks.tsx index c08520126..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, @@ -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 @@ -87,7 +83,7 @@ const MemberLinks: FC = (props: MemberLinksProps) => { }, 1000) } - return canEdit || memberLinks?.links ? ( + return !loading && (canEdit || memberLinks?.links) ? (

Links

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}`