From c53bd00d2c4eef62559b594a3fb98d288a9fe113 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Fri, 10 Nov 2023 15:10:09 +0200 Subject: [PATCH 01/37] TSJR-217 - add principal skills to user profile --- .../skills/MemberSkillsInfo.module.scss | 20 ++++ .../skills/MemberSkillsInfo.tsx | 65 +++++++++---- .../ModifySkillsModal.module.scss | 15 ++- .../ModifySkillsModal/ModifySkillsModal.tsx | 13 ++- .../popular-skills/PopularSkills.tsx | 94 ++----------------- .../src/lib/utils/search-query.tsx | 16 ++-- .../core/lib/profile/data-providers/index.ts | 1 + .../useUserSkillsDisplayModes.ts | 19 ++++ .../profile-functions/profile-store/index.ts | 1 + .../profile-store/profile-endpoint.config.ts | 4 + src/libs/core/lib/profile/user-skill.model.ts | 15 +++ .../InputSkillSelector.tsx | 12 ++- .../use-member-skill-editor.tsx | 83 +++++++++++----- .../lib/components/skill-pill/SkillPill.tsx | 2 +- .../standard-skills.service.ts | 1 + .../input-multiselect/InputMultiselect.tsx | 3 + 16 files changed, 213 insertions(+), 151 deletions(-) create mode 100644 src/libs/core/lib/profile/data-providers/useUserSkillsDisplayModes.ts 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 f07d5098f..22e6cacff 100644 --- a/src/apps/profiles/src/member-profile/skills/MemberSkillsInfo.module.scss +++ b/src/apps/profiles/src/member-profile/skills/MemberSkillsInfo.module.scss @@ -27,8 +27,28 @@ .skillsWrap { padding-bottom: $sp-8; + + :global(.large-subtitle) { + margin-bottom: $sp-4; + } + @include ltemd { padding-bottom: $sp-4; } } } + +.principalSkillsWrap { + background: $black-5; + border-radius: $sp-2; + padding: $sp-6; + + .additionalSkillsWrap { + margin-top: $sp-6; + } +} + +.principalSkills { + display: flex; + flex-wrap: wrap; + gap: $sp-2; +} diff --git a/src/apps/profiles/src/member-profile/skills/MemberSkillsInfo.tsx b/src/apps/profiles/src/member-profile/skills/MemberSkillsInfo.tsx index 622574f0d..d15b62c4f 100644 --- a/src/apps/profiles/src/member-profile/skills/MemberSkillsInfo.tsx +++ b/src/apps/profiles/src/member-profile/skills/MemberSkillsInfo.tsx @@ -1,9 +1,9 @@ import { Dispatch, FC, SetStateAction, useEffect, useMemo, useState } from 'react' import { useSearchParams } from 'react-router-dom' -import { orderBy } from 'lodash' +import { filter, orderBy } from 'lodash' -import { UserProfile, UserSkill } from '~/libs/core' -import { GroupedSkillsUI, HowSkillsWorkModal, isSkillVerified } from '~/libs/shared' +import { UserProfile, UserSkill, UserSkillDisplayModes } from '~/libs/core' +import { GroupedSkillsUI, HowSkillsWorkModal, isSkillVerified, SkillPill } from '~/libs/shared' import { Button } from '~/libs/ui' import { AddButton, EditMemberPropertyBtn, EmptySection } from '../../components' @@ -33,11 +33,19 @@ const MemberSkillsInfo: FC = (props: MemberSkillsInfoProp ['desc', 'asc'], ) as UserSkill[], [props.profile.skills]) + const principalSkills = useMemo(() => ( + filter(memberSkills, s => s.displayMode?.name === UserSkillDisplayModes.principal) + ), [memberSkills]) + + const additionalSkills = useMemo(() => ( + filter(memberSkills, s => s.displayMode?.name !== UserSkillDisplayModes.principal) + ), [memberSkills]) + const groupedSkillsByCategory: { [key: string]: UserSkill[] } = useMemo(() => { const grouped: { [key: string]: UserSkill[] } = {} const sortedGroupedSkillsByCategory: { [key: string]: UserSkill[] } = {} - memberSkills.forEach((skill: UserSkill) => { + additionalSkills.forEach((skill: UserSkill) => { if (grouped[skill.category.name]) { grouped[skill.category.name].push(skill) } else { @@ -52,7 +60,7 @@ const MemberSkillsInfo: FC = (props: MemberSkillsInfoProp }) return sortedGroupedSkillsByCategory - }, [memberSkills]) + }, [additionalSkills]) const [isEditMode, setIsEditMode]: [boolean, Dispatch>] = useState(false) @@ -93,14 +101,6 @@ const MemberSkillsInfo: FC = (props: MemberSkillsInfoProp return (
- { - skillsRenderer && memberSkills.length > 0 && ( -
- {skillsRenderer(memberSkills)} -
- ) - } -

Skills

@@ -122,11 +122,42 @@ const MemberSkillsInfo: FC = (props: MemberSkillsInfoProp
+ { + skillsRenderer && memberSkills.length > 0 && ( +
+ {skillsRenderer(memberSkills)} +
+ ) + } +
- {memberSkills.length > 0 && ( - + {principalSkills.length > 0 && ( +
+
+ Principal Skills +
+
+ {principalSkills.map((skill: UserSkill) => ( + + ))} +
+
+ )} + {additionalSkills.length > 0 && ( +
+ {principalSkills.length > 0 && ( +
+ Additional Skills +
+ )} + +
)} {!memberSkills.length && ( = (props: ModifySkillsModalP onClose={props.onClose} open size='lg' - title='My Skills' + title={( +
+

Your skills

+

+ We use your skills to connect you to the right opportunities. +

+
+ )} buttons={(
- +


NOTE: -  You can add up to {MAX_PRINCIPAL_SKILLS_COUNT} skills to your principal section. +  You can add up to + {' '} + {MAX_PRINCIPAL_SKILLS_COUNT} + {' '} + skills to your principal section.


- To move a skill back to the additional section, just type it in the additional skills input. + To move a skill back to the + {' '} + additional section + , just type it in the + {' '} + additional skills input + .

From 1769697834cc434dc5c03c63566e5d2f84b8f815 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Mon, 13 Nov 2023 13:59:16 +0200 Subject: [PATCH 05/37] TSJR-99 Topcoder Standardized Skills for TCA --- .../CertificationDetailsPage.tsx | 3 + .../CertificationDetailsSidebar.module.scss | 2 + .../CertificationDetailsSidebar.tsx | 49 ++++++++- .../CourseDetailsPage.module.scss | 3 + .../src/course-details/CourseDetailsPage.tsx | 58 +++++++++- .../edit-skills-btn/EditSkillsBtn.module.scss | 12 +++ .../edit-skills-btn/EditSkillsBtn.tsx | 22 ++++ .../lib/components/edit-skills-btn/index.ts | 1 + .../hiring-manager-view/HiringManagerView.tsx | 1 - src/apps/learn/src/lib/components/index.ts | 2 + .../ModifySkillsModal.module.scss | 31 ++++++ .../modify-skills-modal/ModifySkillsModal.tsx | 79 ++++++++++++++ .../components/modify-skills-modal/index.ts | 1 + .../lib/components/skill-tags/SkillTags.tsx | 7 +- .../src/lib/components/skills-editor/index.ts | 1 + .../skills-editor/use-skill-editor.tsx | 100 ++++++++++++++++++ .../courses-provider-data.model.ts | 3 + .../courses-provider/courses.provider.tsx | 3 +- .../courses-provider/learn-course.model.ts | 3 +- .../learn/src/lib/data-providers/index.ts | 1 + .../data-providers/standard-skills/index.ts | 1 + .../standard-skills.service.ts | 25 +++++ .../tca-certification.model.ts | 3 +- .../tca-certification-data.model.ts | 3 + .../tca-certification.provider.tsx | 3 +- .../tca-skill-type.ts | 10 +- .../src/welcome/courses-card/CoursesCard.tsx | 1 - .../cert-card/TCCertCard.tsx | 6 +- src/libs/core/lib/profile/user-skill.model.ts | 1 + .../InputSkillSelector.tsx | 7 +- 30 files changed, 410 insertions(+), 32 deletions(-) create mode 100644 src/apps/learn/src/lib/components/edit-skills-btn/EditSkillsBtn.module.scss create mode 100644 src/apps/learn/src/lib/components/edit-skills-btn/EditSkillsBtn.tsx create mode 100644 src/apps/learn/src/lib/components/edit-skills-btn/index.ts create mode 100644 src/apps/learn/src/lib/components/modify-skills-modal/ModifySkillsModal.module.scss create mode 100644 src/apps/learn/src/lib/components/modify-skills-modal/ModifySkillsModal.tsx create mode 100644 src/apps/learn/src/lib/components/modify-skills-modal/index.ts create mode 100644 src/apps/learn/src/lib/components/skills-editor/index.ts create mode 100644 src/apps/learn/src/lib/components/skills-editor/use-skill-editor.tsx create mode 100644 src/apps/learn/src/lib/data-providers/standard-skills/index.ts create mode 100644 src/apps/learn/src/lib/data-providers/standard-skills/standard-skills.service.ts diff --git a/src/apps/learn/src/certification-details/CertificationDetailsPage.tsx b/src/apps/learn/src/certification-details/CertificationDetailsPage.tsx index 20481cef4..531e1180d 100644 --- a/src/apps/learn/src/certification-details/CertificationDetailsPage.tsx +++ b/src/apps/learn/src/certification-details/CertificationDetailsPage.tsx @@ -43,6 +43,7 @@ const CertificationDetailsPage: FC<{}> = () => { const { certification, ready: certificationReady, + mutate: reloadCertification, }: TCACertificationProviderData = useGetTCACertification(dashedName as string) // Fetch Enrollment status & progress @@ -130,6 +131,8 @@ const CertificationDetailsPage: FC<{}> = () => { certification={certification} enrolled={isEnrolled} certProgress={progress} + profile={profile} + reloadCertification={reloadCertification} /> ) } diff --git a/src/apps/learn/src/certification-details/certification-details-sidebar/CertificationDetailsSidebar.module.scss b/src/apps/learn/src/certification-details/certification-details-sidebar/CertificationDetailsSidebar.module.scss index c85fb590b..cb122832b 100644 --- a/src/apps/learn/src/certification-details/certification-details-sidebar/CertificationDetailsSidebar.module.scss +++ b/src/apps/learn/src/certification-details/certification-details-sidebar/CertificationDetailsSidebar.module.scss @@ -3,6 +3,8 @@ .section-header { margin-top: $sp-6; + display: flex; + align-items: center; } .certificate-placeholder { diff --git a/src/apps/learn/src/certification-details/certification-details-sidebar/CertificationDetailsSidebar.tsx b/src/apps/learn/src/certification-details/certification-details-sidebar/CertificationDetailsSidebar.tsx index 1bc2d1e21..c1886318b 100644 --- a/src/apps/learn/src/certification-details/certification-details-sidebar/CertificationDetailsSidebar.tsx +++ b/src/apps/learn/src/certification-details/certification-details-sidebar/CertificationDetailsSidebar.tsx @@ -1,12 +1,16 @@ -import { FC, ReactNode } from 'react' +import { Dispatch, FC, ReactNode, SetStateAction, useMemo, useState } from 'react' +import { KeyedMutator } from 'swr' import classNames from 'classnames' import { EnvironmentConfig } from '~/config' import { IconOutline, IconSolid, Tooltip } from '~/libs/ui' +import { UserProfile, UserRole } from '~/libs/core' import { CompletionTimeRange, + EditSkillsBtn, LearnLevelIcon, + ModifySkillsModal, ProvidersLogoList, SkillTags, StickySidebar, @@ -24,6 +28,8 @@ interface CertificationDetailsSidebarProps { certification: TCACertification enrolled: boolean certProgress?: TCACertificationProgress + profile: UserProfile | undefined + reloadCertification: KeyedMutator } function renderTooltipContents(icon: ReactNode, text: Array): ReactNode { @@ -57,6 +63,26 @@ const CertificationDetailsSidebar: FC = (props const suggestedRetailPrice: string = product?.metadata?.estimatedRetailPrice || '20' + const canEdit: boolean = useMemo(() => !!props.profile?.roles?.includes(UserRole.tcaAdmin), [props.profile]) + + const [isEditMode, setIsEditMode]: [boolean, Dispatch>] + = useState(false) + + function handleModyfSkillsModalClose(): void { + setIsEditMode(false) + } + + function handleModyfSkillsSave(): void { + setTimeout(() => { + setIsEditMode(false) + props.reloadCertification() + }, 1500) + } + + function handleEditSkillsClick(): void { + setIsEditMode(true) + } + return (
@@ -140,10 +166,17 @@ const CertificationDetailsSidebar: FC = (props
- Skills Covered + Skills Covered + { + canEdit && ( + + ) + }
= (props )}
+ + { + isEditMode && ( + + ) + }
) } diff --git a/src/apps/learn/src/course-details/CourseDetailsPage.module.scss b/src/apps/learn/src/course-details/CourseDetailsPage.module.scss index 1263e54f5..cf8f2a859 100644 --- a/src/apps/learn/src/course-details/CourseDetailsPage.module.scss +++ b/src/apps/learn/src/course-details/CourseDetailsPage.module.scss @@ -42,6 +42,9 @@ @extend .body-main; margin-top: $sp-8; } + .skills-section-header { + margin-top: $sp-8; + } } .description { diff --git a/src/apps/learn/src/course-details/CourseDetailsPage.tsx b/src/apps/learn/src/course-details/CourseDetailsPage.tsx index d0c24604e..cda139736 100644 --- a/src/apps/learn/src/course-details/CourseDetailsPage.tsx +++ b/src/apps/learn/src/course-details/CourseDetailsPage.tsx @@ -1,6 +1,8 @@ +/* eslint-disable complexity */ /* eslint-disable react/no-danger */ -import { FC, ReactNode, useContext, useMemo } from 'react' +import { Dispatch, FC, ReactNode, SetStateAction, useContext, useMemo, useState } from 'react' import { Params, useParams } from 'react-router-dom' +import classNames from 'classnames' import { Breadcrumb, @@ -10,14 +12,17 @@ import { LoadingSpinner, } from '~/libs/ui' import { textFormatGetSafeString } from '~/libs/shared' -import { profileContext, ProfileContextData } from '~/libs/core' +import { profileContext, ProfileContextData, UserRole } from '~/libs/core' import { AllCertificationsProviderData, CoursesProviderData, CourseTitle, + EditSkillsBtn, + ModifySkillsModal, PageTitle, ResourceProviderData, + SkillTags, TCACertificationProgressBox, useGetCertification, useGetCourses, @@ -44,6 +49,7 @@ const CourseDetailsPage: FC<{}> = () => { const { course, ready: courseReady, + mutate: reloadCourse, }: CoursesProviderData = useGetCourses(textFormatGetSafeString(routeParams.provider), routeParams.certification) const { @@ -79,6 +85,11 @@ const CourseDetailsPage: FC<{}> = () => { routeParams.provider, ]) + const canEdit: boolean = useMemo(() => !!profile?.roles?.includes(UserRole.tcaAdmin), [profile]) + + const [isEditMode, setIsEditMode]: [boolean, Dispatch>] + = useState(false) + function getDescription(): ReactNode { if (!course) { @@ -178,6 +189,21 @@ const CourseDetailsPage: FC<{}> = () => { ) } + function handleEditSkillsClick(): void { + setIsEditMode(true) + } + + function handleModyfSkillsModalClose(): void { + setIsEditMode(false) + } + + function handleModyfSkillsSave(): void { + setTimeout(() => { + setIsEditMode(false) + reloadCourse() + }, 1500) + } + return ( {course?.title ?? 'Course Details'} @@ -199,6 +225,24 @@ const CourseDetailsPage: FC<{}> = () => { trackType={certificate?.certificationCategory.track} /> +
+ Skills Covered + { + canEdit && ( + + ) + } +
+ + = () => { />
+ + { + isEditMode && ( + + ) + } )} diff --git a/src/apps/learn/src/lib/components/edit-skills-btn/EditSkillsBtn.module.scss b/src/apps/learn/src/lib/components/edit-skills-btn/EditSkillsBtn.module.scss new file mode 100644 index 000000000..120691ebc --- /dev/null +++ b/src/apps/learn/src/lib/components/edit-skills-btn/EditSkillsBtn.module.scss @@ -0,0 +1,12 @@ +@import "@libs/ui/styles/includes"; + +.editSkillsBtn { + padding: 0 !important; + padding-left: $sp-4 !important; + color: $black-100; + + svg { + width: 16px; + height: 16px; + } +} diff --git a/src/apps/learn/src/lib/components/edit-skills-btn/EditSkillsBtn.tsx b/src/apps/learn/src/lib/components/edit-skills-btn/EditSkillsBtn.tsx new file mode 100644 index 000000000..25d9b3807 --- /dev/null +++ b/src/apps/learn/src/lib/components/edit-skills-btn/EditSkillsBtn.tsx @@ -0,0 +1,22 @@ +import { FC } from 'react' +import classNames from 'classnames' + +import { Button, IconOutline } from '~/libs/ui' + +import styles from './EditSkillsBtn.module.scss' + +interface EditSkillsBtnProps { + className?: string + onClick: () => void +} + +const EditSkillsBtn: FC = (props: EditSkillsBtnProps) => ( + + * + * ``` + * @returns + */ + +export const useSkillEditor = (props: SkillEditorProps): SkillEditor => { + const [skills, setSkills] = useState(props.certification?.skills || props.course?.skills || []) + + // Function that saves the updated skills, will be called from outside + const saveSkills = useCallback(async () => { + if (props.certification) { + await updateTCACertSkills(props.certification, skills) + } + + if (props.course) { + await updateTCACourseSkills(props.course, skills) + } + + }, [props.certification, props.course, skills]) + + // Handle user changes + const handleRemoveSkill = useCallback((skillId: string): void => { + const skill = skills.find(s => s.id === skillId) + if (!skill) { + return + } + + setSkills(skills.filter(s => s.id !== skillId)) + }, [skills]) + + const handleAddSkill = useCallback((skillData: any): void => { + if (skills.find(s => s.id === skillData.value)) { + return + } + + setSkills([...skills, { + category: skillData.category, + description: skillData.description, + id: skillData.value, + name: skillData.label, + }]) + }, [skills]) + + const handleOnChange = useCallback(({ target: { value } }: any): void => { + const removed = differenceWith(skills, value, (s, v: any) => s.id === v.value) + if (removed.length) { + removed.map(s => handleRemoveSkill(s.id)) + } + + const added = differenceWith(value, skills, (v: any, s: any) => v.value === s.skillId) + if (added.length) { + added.forEach(handleAddSkill) + } + }, [handleAddSkill, handleRemoveSkill, skills]) + + // build the form input + const formInput = useMemo(() => ( + + ), [skills, handleOnChange]) + + return { + formInput, + saveSkills, + } +} diff --git a/src/apps/learn/src/lib/data-providers/courses-provider/courses-provider-data.model.ts b/src/apps/learn/src/lib/data-providers/courses-provider/courses-provider-data.model.ts index b80d78557..9b8e8d627 100644 --- a/src/apps/learn/src/lib/data-providers/courses-provider/courses-provider-data.model.ts +++ b/src/apps/learn/src/lib/data-providers/courses-provider/courses-provider-data.model.ts @@ -1,7 +1,10 @@ +import { KeyedMutator } from 'swr' + import { LearnCourse } from './learn-course.model' export interface CoursesProviderData { course?: LearnCourse loading: boolean + mutate: KeyedMutator ready: boolean } diff --git a/src/apps/learn/src/lib/data-providers/courses-provider/courses.provider.tsx b/src/apps/learn/src/lib/data-providers/courses-provider/courses.provider.tsx index 9ef5ce471..384def031 100644 --- a/src/apps/learn/src/lib/data-providers/courses-provider/courses.provider.tsx +++ b/src/apps/learn/src/lib/data-providers/courses-provider/courses.provider.tsx @@ -22,7 +22,7 @@ export function useGetCourses( const url: string = learnUrlGet('courses', `?${params}`) const swrCacheConfig: SWRConfiguration = useSwrCache(url) - const { data, error }: SWRResponse> = useSWR(url, swrCacheConfig) + const { data, error, mutate }: SWRResponse> = useSWR(url, swrCacheConfig) const course: LearnCourse | undefined = get(data, [0]) @@ -32,6 +32,7 @@ export function useGetCourses( return { course, loading: !data && !error, + mutate, ready: !!data || !!error, } } diff --git a/src/apps/learn/src/lib/data-providers/courses-provider/learn-course.model.ts b/src/apps/learn/src/lib/data-providers/courses-provider/learn-course.model.ts index 3c538e4c1..2067470ba 100644 --- a/src/apps/learn/src/lib/data-providers/courses-provider/learn-course.model.ts +++ b/src/apps/learn/src/lib/data-providers/courses-provider/learn-course.model.ts @@ -7,7 +7,6 @@ import { TCASkillType } from '../tca-certifications-provider/tca-skill-type' export interface LearnCourse extends LearnModelBase { certificationId: string completionSuggestions: Array - emsiSkills: Array estimatedCompletionTimeValue: number estimatedCompletionTimeUnits: string fccCourseUuid: string @@ -21,6 +20,6 @@ export interface LearnCourse extends LearnModelBase { modules: Array note: string resourceProvider: ResourceProvider - skills: Array + skills: Array title: string } diff --git a/src/apps/learn/src/lib/data-providers/index.ts b/src/apps/learn/src/lib/data-providers/index.ts index c9fbc91a9..8ba3c060b 100644 --- a/src/apps/learn/src/lib/data-providers/index.ts +++ b/src/apps/learn/src/lib/data-providers/index.ts @@ -3,6 +3,7 @@ export * from './courses-provider' export * from './lesson-provider' export * from './payments' export * from './resource-provider-provider' +export * from './standard-skills' export * from './tca-certifications-provider' export * from './user-certifications-provider' export * from './user-completed-certifications-provider' diff --git a/src/apps/learn/src/lib/data-providers/standard-skills/index.ts b/src/apps/learn/src/lib/data-providers/standard-skills/index.ts new file mode 100644 index 000000000..04a72e058 --- /dev/null +++ b/src/apps/learn/src/lib/data-providers/standard-skills/index.ts @@ -0,0 +1 @@ +export * from './standard-skills.service' diff --git a/src/apps/learn/src/lib/data-providers/standard-skills/standard-skills.service.ts b/src/apps/learn/src/lib/data-providers/standard-skills/standard-skills.service.ts new file mode 100644 index 000000000..12c9f4ef1 --- /dev/null +++ b/src/apps/learn/src/lib/data-providers/standard-skills/standard-skills.service.ts @@ -0,0 +1,25 @@ +import { EnvironmentConfig } from '~/config' +import { xhrPatchAsync } from '~/libs/core' + +import { TCACertification, TCASkillType } from '../tca-certifications-provider' +import { LearnCourse } from '../courses-provider' + +const baseUrl = `${EnvironmentConfig.API.V5}/learning-paths` + +export async function updateTCACertSkills( + certification: TCACertification, + skills: TCASkillType[], +): Promise { + return xhrPatchAsync(`${baseUrl}/topcoder-certifications/${certification.dashedName}`, { + skills: skills.map(skill => skill.id), + }) +} + +export async function updateTCACourseSkills( + course: LearnCourse, + skills: TCASkillType[], +): Promise { + return xhrPatchAsync(`${baseUrl}/courses/${course.id}`, { + skills: skills.map(skill => skill.id), + }) +} diff --git a/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-certification.model.ts b/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-certification.model.ts index ef6e9d0ae..9fa1faa81 100644 --- a/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-certification.model.ts +++ b/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-certification.model.ts @@ -16,7 +16,6 @@ export interface TCACertification { createdAt: Date dashedName: string description: string - emsiSkills: Array id: number introText: string learnerLevel: TCACertificationLearnLevel @@ -26,7 +25,7 @@ export interface TCACertification { providers: Array resourceProviders: Array sequentialCourses: boolean - skills: string[] + skills: Array status: TCACertificationStatus stripeProductId?: string title: string diff --git a/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-certification/tca-certification-data.model.ts b/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-certification/tca-certification-data.model.ts index 29649bc27..cb2102d49 100644 --- a/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-certification/tca-certification-data.model.ts +++ b/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-certification/tca-certification-data.model.ts @@ -1,8 +1,11 @@ +import { KeyedMutator } from 'swr' + import { TCACertification } from '../tca-certification.model' export interface TCACertificationProviderData { certification?: TCACertification error: boolean loading: boolean + mutate: KeyedMutator ready: boolean } diff --git a/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-certification/tca-certification.provider.tsx b/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-certification/tca-certification.provider.tsx index 5547e0809..69b080b17 100644 --- a/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-certification/tca-certification.provider.tsx +++ b/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-certification/tca-certification.provider.tsx @@ -21,7 +21,7 @@ export function useGetTCACertification( ) const swrCacheConfig: SWRConfiguration = useSwrCache(url) - const { data, error }: SWRResponse = useSWR(url, { + const { data, error, mutate }: SWRResponse = useSWR(url, { ...swrCacheConfig, isPaused: () => options?.enabled === false, }) @@ -30,6 +30,7 @@ export function useGetTCACertification( certification: data, error: !!error, loading: !data, + mutate, ready: !!data, } } diff --git a/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-skill-type.ts b/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-skill-type.ts index 4f331194a..9a9c030ca 100644 --- a/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-skill-type.ts +++ b/src/apps/learn/src/lib/data-providers/tca-certifications-provider/tca-skill-type.ts @@ -1,7 +1,3 @@ -export type TCASkillType = { - assessed: boolean - confidence: number - emsiId: string - learned: boolean - name: string -} +import { UserSkill } from '~/libs/core' + +export type TCASkillType = Pick diff --git a/src/apps/learn/src/welcome/courses-card/CoursesCard.tsx b/src/apps/learn/src/welcome/courses-card/CoursesCard.tsx index fb9aad3ad..c58daea0a 100644 --- a/src/apps/learn/src/welcome/courses-card/CoursesCard.tsx +++ b/src/apps/learn/src/welcome/courses-card/CoursesCard.tsx @@ -117,7 +117,6 @@ const CoursesCard: FC = (props: CoursesCardProps) => { courseKey={props.certification.course.key} expandCount={2} skills={props.certification.course.skills} - emsiSkills={props.certification.course.emsiSkills} theme={isCompleted ? 'gray' : 'white'} /> diff --git a/src/apps/learn/src/welcome/tc-certifications/cert-card/TCCertCard.tsx b/src/apps/learn/src/welcome/tc-certifications/cert-card/TCCertCard.tsx index e2b38ecd9..55072860b 100644 --- a/src/apps/learn/src/welcome/tc-certifications/cert-card/TCCertCard.tsx +++ b/src/apps/learn/src/welcome/tc-certifications/cert-card/TCCertCard.tsx @@ -39,11 +39,10 @@ const EXCERPT_TEXT_LEN: number = 165 const TCCertCard: FC = (props: TCCertCardProps) => { const desc: string = props.certification.description.slice(0, EXCERPT_TEXT_LEN) - const { skills, providers, dashedName, emsiSkills }: { - skills: string[], + const { skills, providers, dashedName }: { + skills: TCASkillType[], providers: Array, dashedName: string - emsiSkills: TCASkillType[] } = props.certification const isEnrolled: boolean = props.progress?.status === 'enrolled' @@ -140,7 +139,6 @@ const TCCertCard: FC = (props: TCCertCardProps) => {

+ description?: string | null | undefined } diff --git a/src/libs/shared/lib/components/input-skill-selector/InputSkillSelector.tsx b/src/libs/shared/lib/components/input-skill-selector/InputSkillSelector.tsx index 6883ab2b9..e28c494e2 100644 --- a/src/libs/shared/lib/components/input-skill-selector/InputSkillSelector.tsx +++ b/src/libs/shared/lib/components/input-skill-selector/InputSkillSelector.tsx @@ -3,14 +3,15 @@ import { noop } from 'lodash' import { InputMultiselect, InputMultiselectOption, InputMultiselectThemes } from '~/libs/ui' import { UserSkill } from '~/libs/core' +import { TCASkillType } from '~/apps/learn/src/lib' import { autoCompleteSkills, isSkillVerified } from '../../services/standard-skills' -const mapSkillToInputOption = (skill: UserSkill): InputMultiselectOption => ({ +const mapSkillToInputOption = (skill: UserSkill | TCASkillType): InputMultiselectOption => ({ ...skill, label: skill.name, value: skill.id, - verified: isSkillVerified(skill), + verified: (skill as UserSkill).levels ? isSkillVerified(skill as UserSkill) : undefined, }) interface Option { @@ -36,7 +37,7 @@ interface InputSkillSelectorProps { readonly loading?: boolean readonly onChange?: (event: ChangeEvent) => void readonly placeholder?: string - readonly value?: UserSkill[] + readonly value?: UserSkill[] | TCASkillType[] readonly theme?: InputMultiselectThemes readonly useWrapper?: boolean readonly dropdownIcon?: ReactNode From f6b8eb5ffd32cded511173e087bf346e1f6e1aaf Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Mon, 13 Nov 2023 14:25:22 +0200 Subject: [PATCH 06/37] TSJR-99 remove EMSI skills flag --- src/config/environments/default.env.ts | 1 - src/config/environments/global-config.model.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/config/environments/default.env.ts b/src/config/environments/default.env.ts index e4764c488..fcb5a2f0f 100644 --- a/src/config/environments/default.env.ts +++ b/src/config/environments/default.env.ts @@ -55,7 +55,6 @@ export const URLS = { export const MEMBER_VERIFY_LOOKER = getReactEnv('MEMBER_VERIFY_LOOKER', 3322) export const ENABLE_TCA_CERT_MONETIZATION = false -export const ENABLE_EMSI_SKILLS = getReactEnv('ENABLE_EMSI_SKILLS', false) export const TERMS_URL = 'https://www.topcoder-dev.com/challenges/terms/detail/317cd8f9-d66c-4f2a-8774-63c612d99cd4' export const PRIVACY_POLICY_URL = `${TOPCODER_URL}/policy` diff --git a/src/config/environments/global-config.model.ts b/src/config/environments/global-config.model.ts index 8e6f23699..cd2db4c81 100644 --- a/src/config/environments/global-config.model.ts +++ b/src/config/environments/global-config.model.ts @@ -29,7 +29,6 @@ export interface GlobalConfig { } MEMBER_VERIFY_LOOKER: number ENABLE_TCA_CERT_MONETIZATION: boolean - ENABLE_EMSI_SKILLS: boolean VANILLA_FORUM: { ACCESS_TOKEN: string V2_URL: string From f2c0b16eb03e5eeac4f23b8c95239dd5c2c41187 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 13 Nov 2023 17:45:29 +0200 Subject: [PATCH 07/37] Typo update --- .../PrincipalSkillsModal/PrincipalSkillsModal.tsx | 12 ++++++------ .../member-skill-editor/use-member-skill-editor.tsx | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/apps/profiles/src/member-profile/skills/PrincipalSkillsModal/PrincipalSkillsModal.tsx b/src/apps/profiles/src/member-profile/skills/PrincipalSkillsModal/PrincipalSkillsModal.tsx index b6ad914c5..0de070d00 100644 --- a/src/apps/profiles/src/member-profile/skills/PrincipalSkillsModal/PrincipalSkillsModal.tsx +++ b/src/apps/profiles/src/member-profile/skills/PrincipalSkillsModal/PrincipalSkillsModal.tsx @@ -16,14 +16,14 @@ const PrincipalSkillsModal: FC = (props: PrincipalSki

Now you can highlight your most important skills using the  - Principal skills + Principal Skills  section!
@@ -32,7 +32,7 @@ const PrincipalSkillsModal: FC = (props: PrincipalSki

Just move the skills you want to highlight by typing them in the  - principal skills input + Principal Skills input  when you edit your skills.
@@ -44,16 +44,16 @@ const PrincipalSkillsModal: FC = (props: PrincipalSki {' '} {MAX_PRINCIPAL_SKILLS_COUNT} {' '} - skills to your principal section. + skills to your Principal Skills section.


To move a skill back to the {' '} - additional section + Additional Skills section , just type it in the {' '} - additional skills input + Additional Skills input .

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 44c74ae41..75d5dc509 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 @@ -150,7 +150,7 @@ export const useMemberSkillEditor = ({ These will be showcased at the top of your profile.

Date: Mon, 13 Nov 2023 18:27:39 +0200 Subject: [PATCH 08/37] TSJR-99 update wording on TCA skills modal --- .../lib/components/modify-skills-modal/ModifySkillsModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/learn/src/lib/components/modify-skills-modal/ModifySkillsModal.tsx b/src/apps/learn/src/lib/components/modify-skills-modal/ModifySkillsModal.tsx index 62138c4bd..78c545288 100644 --- a/src/apps/learn/src/lib/components/modify-skills-modal/ModifySkillsModal.tsx +++ b/src/apps/learn/src/lib/components/modify-skills-modal/ModifySkillsModal.tsx @@ -66,7 +66,7 @@ const ModifySkillsModal: FC = (props: ModifySkillsModalP >

- TCA skills will be associated to members upon completion of courses & certifications. + Members will earn verified skills upon completion of TCA courses & certifications.

{editor.formInput} From 1f6eec65358ba4cd2529cf6cab9294a535aa7608 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 14 Nov 2023 17:44:50 +0200 Subject: [PATCH 09/37] MP-356 - initial member stats --- src/apps/profiles/src/hooks/index.ts | 1 + .../src/hooks/useFetchActiveTracks.tsx | 150 ++++++++++++++++++ .../MemberRolesInfoModal/index.ts | 1 - .../MemberStats/MemberStats.module.scss | 98 ++++++++++++ .../MemberStats/MemberStats.tsx | 76 +++++++++ .../tc-achievements/MemberStats/index.ts | 1 + .../MemberStats/winner-icon.svg | 3 + .../MemberTCAchievements.module.scss | 36 +---- .../tc-achievements/MemberTCAchievements.tsx | 50 +----- .../MemberRolesInfoModal.tsx | 0 .../MemberRolesInfoModal/index.ts | 1 + .../TcSpecialRolesBanner.module.scss | 35 ++++ .../TcSpecialRolesBanner.tsx | 43 +++++ .../TcSpecialRolesBanner/index.ts | 1 + src/libs/core/lib/profile/user-stats.model.ts | 3 + 15 files changed, 418 insertions(+), 81 deletions(-) create mode 100644 src/apps/profiles/src/hooks/useFetchActiveTracks.tsx delete mode 100644 src/apps/profiles/src/member-profile/tc-achievements/MemberRolesInfoModal/index.ts create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/MemberStats/MemberStats.module.scss create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/MemberStats/MemberStats.tsx create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/MemberStats/index.ts create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/MemberStats/winner-icon.svg rename src/apps/profiles/src/member-profile/tc-achievements/{ => TcSpecialRolesBanner}/MemberRolesInfoModal/MemberRolesInfoModal.tsx (100%) create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/index.ts create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.module.scss create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.tsx create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/index.ts diff --git a/src/apps/profiles/src/hooks/index.ts b/src/apps/profiles/src/hooks/index.ts index 304fa3400..8efd7407a 100644 --- a/src/apps/profiles/src/hooks/index.ts +++ b/src/apps/profiles/src/hooks/index.ts @@ -1,2 +1,3 @@ +export * from './useFetchActiveTracks' export * from './useRatingDistroOptions' export * from './useRatingHistoryOptions' diff --git a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx new file mode 100644 index 000000000..d103148d3 --- /dev/null +++ b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx @@ -0,0 +1,150 @@ +import { filter, orderBy } from 'lodash' +import { useMemo } from 'react' + +import { MemberStats, useMemberStats, UserStats } from '~/libs/core' + +export const useFetchActiveTracks = (userHandle: string): any => { + const memberStats: UserStats | undefined = useMemberStats(userHandle) + + const designSubTracks: {[key: string]: MemberStats} = useMemo(() => ( + memberStats?.DESIGN?.subTracks.reduce((all, subTrack) => { + all[subTrack.name] = subTrack + return all + }, {} as {[key: string]: MemberStats}) ?? {} + ), [memberStats]) + + const developSubTracks: {[key: string]: MemberStats} = useMemo(() => ( + memberStats?.DEVELOP?.subTracks.reduce((all, subTrack) => { + all[subTrack.name] = subTrack + return all + }, {} as {[key: string]: MemberStats}) ?? {} + ), [memberStats]) + + // Design + const designTrackStats = useMemo(() => { + const designStats = developSubTracks.DESIGN + const designF2FStats = designSubTracks.DESIGN_FIRST_2_FINISH + const webDesignStats = designSubTracks.WEB_DESIGNS + const logoDesignStats = designSubTracks.LOGO_DESIGN + const wireframesStats = designSubTracks.WIREFRAMES + const frontEndFlashStats = designSubTracks.FRONT_END_FLASH + const printPresentationStats = designSubTracks.PRINT_OR_PRESENTATION + const studioOtherStats = designSubTracks.STUDIO_OTHER + const feDesignStats = designSubTracks.APPLICATION_FRONT_END_DESIGN + const bannersIconsStats = designSubTracks.BANNERS_OR_ICONS + const widgetMobileStats = designSubTracks.WIDGET_OR_MOBILE_SCREEN_DESIGN + + const subTracks = [ + designStats, + designF2FStats, + webDesignStats, + logoDesignStats, + wireframesStats, + frontEndFlashStats, + printPresentationStats, + studioOtherStats, + feDesignStats, + bannersIconsStats, + widgetMobileStats, + ] + + const totalWins = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.wins || 0)), 0) + const challengesCount = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.challenges || 0)), 0) + const submissionsCount = subTracks.reduce((sum, subTrack) => ( + sum + (subTrack?.submissions?.submissions || 0) + ), 0) + + return { + isActive: challengesCount > 0, + name: 'Design', + submissions: submissionsCount, + wins: totalWins, + } + }, [developSubTracks, designSubTracks]) + + // Development + const developTrackStats = useMemo(() => { + const developmentStats = developSubTracks.DEVELOPMENT + const architectureStats = developSubTracks.ARCHITECTURE + const f2fStats = developSubTracks.FIRST_2_FINISH + const codeStats = developSubTracks.CODE + const assemblyStats = developSubTracks.ASSEMBLY_COMPETITION + const uiPrototypeStats = developSubTracks.UI_PROTOTYPE_COMPETITION + + const subTracks = [ + developmentStats, + architectureStats, + f2fStats, + codeStats, + assemblyStats, + uiPrototypeStats, + ] + + const totalWins = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.wins || 0)), 0) + const challengesCount = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.challenges || 0)), 0) + const submissionsCount = subTracks.reduce((sum, subTrack) => ( + sum + (subTrack?.submissions?.submissions || 0) + ), 0) + + return { + isActive: challengesCount > 0, + name: 'Development', + submissions: submissionsCount, + wins: totalWins, + } + }, [developSubTracks]) + + // Testing + const testingTrackStats = useMemo(() => { + const bugHuntStats = developSubTracks.BUG_HUNT + const testScenStats = developSubTracks.TEST_SCENARIOS + const testSuitesStats = developSubTracks.TEST_SUITES + + const subTracks = [ + bugHuntStats, + testScenStats, + testSuitesStats, + ] + + const totalWins = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.wins || 0)), 0) + const challengesCount = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.challenges || 0)), 0) + const submissionsCount = subTracks.reduce((sum, subTrack) => ( + sum + (subTrack?.submissions?.submissions || 0) + ), 0) + + return { + isActive: challengesCount > 0, + name: 'Testing', + submissions: submissionsCount, + wins: totalWins, + } + }, [developSubTracks]) + + // Competitive Programming + const cpTrackStats = useMemo(() => ({ + isActive: (memberStats?.DATA_SCIENCE?.challenges ?? 0) > 0, + name: 'Competitive Programming', + ranking: Math.max( + memberStats?.DATA_SCIENCE?.MARATHON_MATCH.rank.percentile ?? 0, + memberStats?.DATA_SCIENCE?.SRM.rank.percentile ?? 0, + ), + wins: memberStats?.DATA_SCIENCE?.wins ?? 0, + }), [memberStats]) + + // copilot + // const copilotTrackStats = useMemo(() => ({ + // isActive: (memberStats?.DATA_SCIENCE?.challenges ?? 0) > 0, + // ranking: Math.max( + // memberStats?.DATA_SCIENCE?.MARATHON_MATCH.rank.percentile ?? 0, + // memberStats?.DATA_SCIENCE?.SRM.rank.percentile ?? 0, + // ), + // wins: memberStats?.DATA_SCIENCE?.wins ?? 0, + // }), [memberStats]) + + return orderBy(filter([ + cpTrackStats, + designTrackStats, + developTrackStats, + testingTrackStats, + ], { isActive: true }), ['wins', 'submissions'], 'desc') +} diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberRolesInfoModal/index.ts b/src/apps/profiles/src/member-profile/tc-achievements/MemberRolesInfoModal/index.ts deleted file mode 100644 index 00703a289..000000000 --- a/src/apps/profiles/src/member-profile/tc-achievements/MemberRolesInfoModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as MemberRatingInfoModal } from './MemberRolesInfoModal' diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/MemberStats.module.scss b/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/MemberStats.module.scss new file mode 100644 index 000000000..3dff5d4ea --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/MemberStats.module.scss @@ -0,0 +1,98 @@ +@import '@libs/ui/styles/includes'; +@import "@libs/ui/styles/includes"; + + +.containerWrap { + flex: 1; +} + +.container { + display: flex; + flex-direction: column; + border-radius: 15px; + background-image: linear-gradient(90deg, #7B21A7, #1974AD); + color: $tc-white; + padding: $sp-8; + min-height: 100%; + + @include ltelg { + padding: $sp-4; + } + + :global(.body-large-bold) { + text-align: center; + } +} + +.sectionTitle { + text-align: center; + margin-bottom: $sp-3; +} + +.footerNote { + margin-top: $sp-4; +} + +.innerWrapper { + display: flex; + flex-direction: column; + width: 100%; + flex: 1 1 auto; +} + +.statsList { + margin: auto 0; +} + +.trackListItem { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 62px; + padding: $sp-2 $sp-3; + background: rgba($tc-white, 0.05); + border: 1px solid rgba($tc-white, 0.25); + border-radius: $sp-2; + transition: 0.15s ease-in; + cursor: pointer; + + + .trackListItem { + margin-top: $sp-1; + } + + &:hover { + background: rgba($tc-white, 0.15); + border-color: rgba($tc-white, 0.35); + } +} + +.trackDetails { + display: flex; + align-items: center; + > svg { + flex: 0 0 auto; + } +} + +.rightArrowIcon { + margin-left: $sp-3; +} + +.trackStats { + display: flex; + flex-direction: column; + text-align: right; + margin-left: $sp-1; + .count { + font-family: $font-barlow-condensed; + font-weight: $font-weight-medium; + font-size: 26px; + line-height: 34px; + } + .label { + font-family: $font-roboto; + font-weight: $font-weight-medium; + font-size: 11px; + line-height: 12px; + } +} diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/MemberStats.tsx b/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/MemberStats.tsx new file mode 100644 index 000000000..90e2aaebb --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/MemberStats.tsx @@ -0,0 +1,76 @@ +import { FC } from 'react' +import classNames from 'classnames' + +import { UserProfile } from '~/libs/core' +import { IconOutline } from '~/libs/ui' + +import { useFetchActiveTracks } from '../../../hooks' + +import { ReactComponent as WinnerIcon } from './winner-icon.svg' +import styles from './MemberStats.module.scss' + +interface MemberStatsProps { + profile: UserProfile +} + +const MemberStats: FC = props => { + const activeTracks = useFetchActiveTracks(props.profile.handle) + + return activeTracks?.length === 0 ? <> : ( +
+
+
+

+ + Member Stats + +

+
    + {activeTracks.map((track: any) => ( +
  • + {track.name} +
    + {!track.ranking && ((track.submissions || track.wins) > 0) && ( + <> + + + + {track.wins || track.submissions} + + + {track.wins > 0 ? 'Wins' : 'Submissions'} + + + + )} + {track.ranking !== undefined && ( + + + {track.ranking} + % + + + Ranking + + + )} + +
    +
  • + ))} +
+

+ + Topcoder challenges are open competitions where community + members participate in small units of work to deliver projects. + +

+
+
+
+ ) +} + +export default MemberStats diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/index.ts b/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/index.ts new file mode 100644 index 000000000..c18662ca6 --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/index.ts @@ -0,0 +1 @@ +export { default as MemberStats } from './MemberStats' diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/winner-icon.svg b/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/winner-icon.svg new file mode 100644 index 000000000..f836f6b2f --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/winner-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.module.scss b/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.module.scss index 8a13bc3d2..1b89e9d06 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.module.scss +++ b/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.module.scss @@ -35,38 +35,4 @@ } } } - - .rolesSection { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - padding: $sp-4; - background-image: linear-gradient(to right, #652385, #8C384C); - color: $tc-white; - border-radius: 8px; - margin: $sp-4 0; - - @include ltesm { - margin-bottom: 0; - flex-direction: column; - align-items: flex-start; - } - - .rolesWrap { - display: flex; - align-items: center; - } - - .link { - font-size: 14px; - line-height: 14px; - font-weight: $font-weight-medium; - font-family: $font-roboto; - - @include ltesm { - margin-top: $sp-2; - } - } - } -} \ No newline at end of file +} diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx b/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx index fb238586b..3d2937fc9 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx +++ b/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx @@ -1,4 +1,4 @@ -import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react' +import { FC, useMemo } from 'react' import { useMemberBadges, @@ -12,8 +12,8 @@ import { import { CommunityAwards } from '../community-awards' import { TCOWinsBanner } from './TCOWinsBanner' -import { ChallengeWinsBanner } from './ChallengeWinsBanner' -import MemberRolesInfoModal from './MemberRolesInfoModal/MemberRolesInfoModal' +import { TcSpecialRolesBanner } from './TcSpecialRolesBanner' +import { MemberStats } from './MemberStats' import styles from './MemberTCAchievements.module.scss' interface MemberTCAchievementsProps { @@ -38,19 +38,6 @@ const MemberTCAchievements: FC = (props: MemberTCAchi (badge: UserBadge) => /TCO.*Trip Winner/.test(badge.org_badge.badge_name), ).length || 0, [memberBadges]) - const isCopilot: boolean - = useMemo(() => !!memberStats?.COPILOT, [memberStats]) - - const [isInfoModalOpen, setIsInfoModalOpen]: [boolean, Dispatch>] = useState(false) - - function handleInfoModalClose(): void { - setIsInfoModalOpen(false) - } - - function handleInfoModalOpen(): void { - setIsInfoModalOpen(true) - } - return memberStats?.wins || tcoWins || tcoQualifications ? (

Achievements @ Topcoder

@@ -61,37 +48,10 @@ const MemberTCAchievements: FC = (props: MemberTCAchi ) } - { - !!memberStats?.wins && memberStats.wins > 0 && ( - - ) - } +
- { - isCopilot && ( -
-
-

Topcoder Special Roles: 

-

Copilot

-
- - - { - isInfoModalOpen && ( - - ) - } -
- ) - } +
diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberRolesInfoModal/MemberRolesInfoModal.tsx b/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/MemberRolesInfoModal.tsx similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/MemberRolesInfoModal/MemberRolesInfoModal.tsx rename to src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/MemberRolesInfoModal.tsx diff --git a/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/index.ts b/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/index.ts new file mode 100644 index 000000000..a61d2c987 --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/index.ts @@ -0,0 +1 @@ +export { default as MemberRolesInfoModal } from './MemberRolesInfoModal' diff --git a/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.module.scss b/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.module.scss new file mode 100644 index 000000000..ee88a4b6b --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.module.scss @@ -0,0 +1,35 @@ +@import '@libs/ui/styles/includes'; + +.rolesSection { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + padding: $sp-4; + background-image: linear-gradient(to right, #652385, #8C384C); + color: $tc-white; + border-radius: 8px; + margin: $sp-4 0; + + @include ltesm { + margin-bottom: 0; + flex-direction: column; + align-items: flex-start; + } +} + +.rolesWrap { + display: flex; + align-items: center; +} + +.link { + font-size: 14px; + line-height: 14px; + font-weight: $font-weight-medium; + font-family: $font-roboto; + + @include ltesm { + margin-top: $sp-2; + } +} diff --git a/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.tsx b/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.tsx new file mode 100644 index 000000000..cecc885e8 --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.tsx @@ -0,0 +1,43 @@ +import { FC, useMemo, useState } from 'react' + +import { UserStats } from '~/libs/core' + +import { MemberRolesInfoModal } from './MemberRolesInfoModal' +import styles from './TcSpecialRolesBanner.module.scss' + +interface TcSpecialRolesBannerProps { + memberStats: UserStats | undefined +} + +const TcSpecialRolesBanner: FC = props => { + const isCopilot: boolean + = useMemo(() => !!props.memberStats?.COPILOT, [props.memberStats]) + + const [isInfoModalOpen, setIsInfoModalOpen] = useState(false) + + function handleInfoModalClose(): void { + setIsInfoModalOpen(false) + } + + function handleInfoModalOpen(): void { + setIsInfoModalOpen(true) + } + + return !isCopilot ? <> : ( +
+
+

Topcoder Special Roles: 

+

Copilot

+
+ + + {isInfoModalOpen && ( + + )} +
+ ) +} + +export default TcSpecialRolesBanner diff --git a/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/index.ts b/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/index.ts new file mode 100644 index 000000000..ce05632eb --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/index.ts @@ -0,0 +1 @@ +export { default as TcSpecialRolesBanner } from './TcSpecialRolesBanner' diff --git a/src/libs/core/lib/profile/user-stats.model.ts b/src/libs/core/lib/profile/user-stats.model.ts index fb301fc6e..754d8964a 100644 --- a/src/libs/core/lib/profile/user-stats.model.ts +++ b/src/libs/core/lib/profile/user-stats.model.ts @@ -46,6 +46,9 @@ export type MemberStats = { screeningSuccessRate: number wins: number name: string + submissions: { + submissions: number + } } export type UserStats = { From 34672d3ea6f4b663456ec853eb4e090cbf89af7f Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Nov 2023 09:48:59 +0200 Subject: [PATCH 10/37] MP-356 - work on member profile stats & history --- .../ChallengeWin/ChallengeWin.module.scss | 0 .../ChallengeWin/ChallengeWin.tsx | 0 .../ChallengeWinsBanner/ChallengeWin/index.ts | 0 .../ChallengeWinsBanner.module.scss | 0 .../ChallengeWinsBanner.tsx | 430 ++++++++++++++ .../ChallengeWinsBanner/index.ts | 0 .../MemberStatsBlock.module.scss} | 1 - .../MemberStatsBlock/MemberStatsBlock.tsx} | 21 +- .../tc-achievements/MemberStatsBlock/index.ts | 1 + .../StatsDetailsLayout.module.scss | 23 + .../StatsDetailsLayout/StatsDetailsLayout.tsx | 42 ++ .../StatsDetailsLayout/index.ts | 1 + .../StatsNavHeader/StatsNavHeader.module.scss | 7 + .../StatsNavHeader/StatsNavHeader.tsx | 34 ++ .../tc-achievements/StatsNavHeader/index.ts | 1 + .../StatsSummaryBlock.module.scss | 36 ++ .../StatsSummaryBlock/StatsSummaryBlock.tsx | 71 +++ .../StatsSummaryBlock/index.ts | 1 + .../SubTrackSummaryCard.module.scss | 39 ++ .../SubTrackSummaryCard.tsx | 48 ++ .../SubTrackSummaryCard/index.ts | 1 + .../TCOWinsBanner/TCOWinsBanner.module.scss | 0 .../TCOWinsBanner/TCOWinsBanner.tsx | 0 .../TCOWinsBanner/assets/tco-logo-banner.png | Bin .../tc-achievements/TCOWinsBanner/index.ts | 0 .../MemberRolesInfoModal.tsx | 0 .../MemberRolesInfoModal/index.ts | 0 .../TcSpecialRolesBanner.module.scss | 0 .../TcSpecialRolesBanner.tsx | 0 .../TcSpecialRolesBanner/index.ts | 0 .../src/hooks/useFetchActiveTracks.tsx | 211 +++---- .../assets}/winner-icon.svg | 0 src/apps/profiles/src/lib/helpers.ts | 4 + src/apps/profiles/src/lib/index.ts | 1 + .../ChallengeWinsBanner.tsx | 531 ------------------ .../tc-achievements/MemberStats/index.ts | 1 - .../MemberTCAchievements.module.scss | 20 - .../tc-achievements/MemberTCAchievements.tsx | 58 +- .../DefaultAchievementsView.module.scss | 21 + .../DefaultAchievementsView.tsx | 41 ++ .../default-achievements-view/index.ts | 1 + .../sub-track-view/SubTrackView.module.scss | 0 .../sub-track-view/SubTrackView.tsx | 44 ++ .../tc-achievements/sub-track-view/index.ts | 1 + .../track-view/TrackView.module.scss | 12 + .../tc-achievements/track-view/TrackView.tsx | 51 ++ .../tc-achievements/track-view/index.ts | 1 + src/apps/profiles/src/profiles.routes.tsx | 13 + 48 files changed, 1084 insertions(+), 684 deletions(-) rename src/apps/profiles/src/{member-profile => components}/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.module.scss (100%) rename src/apps/profiles/src/{member-profile => components}/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.tsx (100%) rename src/apps/profiles/src/{member-profile => components}/tc-achievements/ChallengeWinsBanner/ChallengeWin/index.ts (100%) rename src/apps/profiles/src/{member-profile => components}/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.module.scss (100%) create mode 100644 src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx rename src/apps/profiles/src/{member-profile => components}/tc-achievements/ChallengeWinsBanner/index.ts (100%) rename src/apps/profiles/src/{member-profile/tc-achievements/MemberStats/MemberStats.module.scss => components/tc-achievements/MemberStatsBlock/MemberStatsBlock.module.scss} (97%) rename src/apps/profiles/src/{member-profile/tc-achievements/MemberStats/MemberStats.tsx => components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx} (82%) create mode 100644 src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/index.ts create mode 100644 src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.module.scss create mode 100644 src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx create mode 100644 src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/index.ts create mode 100644 src/apps/profiles/src/components/tc-achievements/StatsNavHeader/StatsNavHeader.module.scss create mode 100644 src/apps/profiles/src/components/tc-achievements/StatsNavHeader/StatsNavHeader.tsx create mode 100644 src/apps/profiles/src/components/tc-achievements/StatsNavHeader/index.ts create mode 100644 src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.module.scss create mode 100644 src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx create mode 100644 src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/index.ts create mode 100644 src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.module.scss create mode 100644 src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.tsx create mode 100644 src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/index.ts rename src/apps/profiles/src/{member-profile => components}/tc-achievements/TCOWinsBanner/TCOWinsBanner.module.scss (100%) rename src/apps/profiles/src/{member-profile => components}/tc-achievements/TCOWinsBanner/TCOWinsBanner.tsx (100%) rename src/apps/profiles/src/{member-profile => components}/tc-achievements/TCOWinsBanner/assets/tco-logo-banner.png (100%) rename src/apps/profiles/src/{member-profile => components}/tc-achievements/TCOWinsBanner/index.ts (100%) rename src/apps/profiles/src/{member-profile => components}/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/MemberRolesInfoModal.tsx (100%) rename src/apps/profiles/src/{member-profile => components}/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/index.ts (100%) rename src/apps/profiles/src/{member-profile => components}/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.module.scss (100%) rename src/apps/profiles/src/{member-profile => components}/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.tsx (100%) rename src/apps/profiles/src/{member-profile => components}/tc-achievements/TcSpecialRolesBanner/index.ts (100%) rename src/apps/profiles/src/{member-profile/tc-achievements/MemberStats => lib/assets}/winner-icon.svg (100%) delete mode 100644 src/apps/profiles/src/member-profile/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx delete mode 100644 src/apps/profiles/src/member-profile/tc-achievements/MemberStats/index.ts create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/default-achievements-view/DefaultAchievementsView.module.scss create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/default-achievements-view/DefaultAchievementsView.tsx create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/default-achievements-view/index.ts create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.module.scss create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/index.ts create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.module.scss create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx create mode 100644 src/apps/profiles/src/member-profile/tc-achievements/track-view/index.ts diff --git a/src/apps/profiles/src/member-profile/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.module.scss b/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.module.scss similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.module.scss rename to src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.module.scss diff --git a/src/apps/profiles/src/member-profile/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.tsx b/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.tsx similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.tsx rename to src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.tsx diff --git a/src/apps/profiles/src/member-profile/tc-achievements/ChallengeWinsBanner/ChallengeWin/index.ts b/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/index.ts similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/ChallengeWinsBanner/ChallengeWin/index.ts rename to src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/index.ts diff --git a/src/apps/profiles/src/member-profile/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.module.scss b/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.module.scss similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.module.scss rename to src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.module.scss diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx b/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx new file mode 100644 index 000000000..22d24642d --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx @@ -0,0 +1,430 @@ +/* eslint-disable */ +import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react' +import { bind } from 'lodash' + +import { MemberStats, UserProfile, UserStats } from '~/libs/core' + +import { subTrackLabelToHumanName } from '../../../lib/helpers' +import { + AssemblyDetailsModal, + BugHuntDetailsModal, + CodeDetailsModal, + ContentCreationDetailsModal, + CopilotDetailsModal, + DesignF2FDetailsModal, + F2FDetailsModal, + GenericSubtrackDetailsModal, + LogoDesignDetailsModal, + MMDetailsModal, + SRMDetailsModal, + TestScenariosDetailsModal, + UIPrototypeDetailsModal, + WebDesignDetailsModal, +} from '../../../components' + +import { ChallengeWin } from './ChallengeWin' +import styles from './ChallengeWinsBanner.module.scss' + +interface ChallengeWinsBannerProps { + memberStats: UserStats + profile: UserProfile +} + +const ChallengeWinsBanner: FC = (props: ChallengeWinsBannerProps) => { + const contentCreationStats: MemberStats | undefined + = useMemo( + () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'CONTENT_CREATION'), + [props.memberStats], + ) + const specStats: MemberStats | undefined + = useMemo( + () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'SPECIFICATION'), + [props.memberStats], + ) + const copilotPostingStats: MemberStats | undefined + = useMemo( + () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'COPILOT_POSTING'), + [props.memberStats], + ) + const conceptStats: MemberStats | undefined + = useMemo( + () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'CONCEPTUALIZATION'), + [props.memberStats], + ) + + const [modalVisibilityMap, setModalVisibilityMap]: [ + { [key: string]: boolean }, + Dispatch> + ] = useState<{ [key: string]: boolean }>({ + APPLICATION_FRONT_END_DESIGN: false, + ARCHITECTURE: false, + ASSEMBLY_COMPETITION: false, + BANNERS_OR_ICONS: false, + BUG_HUNT: false, + CODE: false, + CONCEPTUALIZATION: false, + CONTENT_CREATION: false, + COPILOT: false, + COPILOT_POSTING: false, + DESIGN: false, + DESIGN_FIRST_2_FINISH: false, + DEVELOPMENT: false, + DS: false, + FIRST_2_FINISH: false, + FRONT_END_FLASH: false, + LOGO_DESIGN: false, + PRINT_OR_PRESENTATION: false, + SPECIFICATION: false, + SRM: false, + STUDIO_OTHER: false, + TEST_SCENARIOS: false, + TEST_SUITES: false, + UI_PROTOTYPE_COMPETITION: false, + WEB_DESIGNS: false, + WIDGET_OR_MOBILE_SCREEN_DESIGN: false, + WIREFRAMES: false, + }) + + function handleChallengeWinModalToggle(subTrack: string): void { + setModalVisibilityMap({ + ...modalVisibilityMap, + [subTrack]: !modalVisibilityMap[subTrack], + }) + } + + return <> + // ( + //
+ //
+ //
+ //

Topcoder Challenge Winner

+ //
+ // { + // !!props.memberStats.DATA_SCIENCE?.SRM?.wins && ( + // + // ) + // } + // { + // !!props.memberStats.DATA_SCIENCE?.MARATHON_MATCH?.wins && ( + // + // ) + // } + // { + // !!props.memberStats.DEVELOP?.wins + // && props.memberStats.DEVELOP?.subTracks.map((ms: MemberStats) => (ms.wins ? ( + // + // ) : undefined)) + // } + // { + // !!props.memberStats.DESIGN?.wins + // && props.memberStats.DESIGN?.subTracks.map((ms: MemberStats) => (ms.wins ? ( + // + // ) : undefined)) + // } + // { + // !!props.memberStats.COPILOT && ( + // + // ) + // } + //
+ //

+ // Topcoder challenges are open competitions where community + // members participate in small units of work to deliver projects. + //

+ //
+ //
+ + // {modalVisibilityMap.SRM && ( + // + // )} + + // {modalVisibilityMap.DS && ( + // + // )} + + // {modalVisibilityMap.CODE && ( + // + // )} + + // {modalVisibilityMap.FIRST_2_FINISH && ( + // + // )} + + // {modalVisibilityMap.ASSEMBLY_COMPETITION && ( + // + // )} + + // {modalVisibilityMap.CONTENT_CREATION && ( + // + // )} + + // {modalVisibilityMap.UI_PROTOTYPE_COMPETITION && ( + // + // )} + + // {modalVisibilityMap.WEB_DESIGNS && ( + // + // )} + + // {modalVisibilityMap.LOGO_DESIGN && ( + // + // )} + + // {modalVisibilityMap.DESIGN_FIRST_2_FINISH && ( + // + // )} + + // {modalVisibilityMap.TEST_SCENARIOS && ( + // + // )} + + // {modalVisibilityMap.BUG_HUNT && ( + // + // )} + + // {modalVisibilityMap.COPILOT && ( + // + // )} + + // {modalVisibilityMap.WIREFRAMES && ( + // + // )} + + // {modalVisibilityMap.FRONT_END_FLASH && ( + // + // )} + + // {modalVisibilityMap.PRINT_OR_PRESENTATION && ( + // + // )} + + // {modalVisibilityMap.STUDIO_OTHER && ( + // + // )} + + // {modalVisibilityMap.APPLICATION_FRONT_END_DESIGN && ( + // + // )} + + // {modalVisibilityMap.BANNERS_OR_ICONS && ( + // + // )} + + // {modalVisibilityMap.WIDGET_OR_MOBILE_SCREEN_DESIGN && ( + // + // )} + + // {modalVisibilityMap.TEST_SUITES && ( + // + // )} + + // {modalVisibilityMap.SPECIFICATION && ( + // + // )} + + // {modalVisibilityMap.DEVELOPMENT && ( + // + // )} + + // {modalVisibilityMap.ARCHITECTURE && ( + // + // )} + + // {modalVisibilityMap.COPILOT_POSTING && ( + // + // )} + + // {modalVisibilityMap.DESIGN && ( + // + // )} + + // {modalVisibilityMap.CONCEPTUALIZATION && ( + // + // )} + + //
+ // ) +} + +export default ChallengeWinsBanner diff --git a/src/apps/profiles/src/member-profile/tc-achievements/ChallengeWinsBanner/index.ts b/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/index.ts similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/ChallengeWinsBanner/index.ts rename to src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/index.ts diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/MemberStats.module.scss b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.module.scss similarity index 97% rename from src/apps/profiles/src/member-profile/tc-achievements/MemberStats/MemberStats.module.scss rename to src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.module.scss index 3dff5d4ea..141914ed3 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/MemberStats.module.scss +++ b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.module.scss @@ -1,4 +1,3 @@ -@import '@libs/ui/styles/includes'; @import "@libs/ui/styles/includes"; diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/MemberStats.tsx b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx similarity index 82% rename from src/apps/profiles/src/member-profile/tc-achievements/MemberStats/MemberStats.tsx rename to src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx index 90e2aaebb..5ff513519 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/MemberStats.tsx +++ b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx @@ -1,19 +1,21 @@ import { FC } from 'react' +import { Link } from 'react-router-dom' import classNames from 'classnames' import { UserProfile } from '~/libs/core' import { IconOutline } from '~/libs/ui' import { useFetchActiveTracks } from '../../../hooks' +import { getUserProfileStatsRoute } from '../../../profiles.routes' -import { ReactComponent as WinnerIcon } from './winner-icon.svg' -import styles from './MemberStats.module.scss' +import styles from './MemberStatsBlock.module.scss' +import { WinnerIcon } from '../../../lib' -interface MemberStatsProps { +interface MemberStatsBlockProps { profile: UserProfile } -const MemberStats: FC = props => { +const MemberStatsBlock: FC = props => { const activeTracks = useFetchActiveTracks(props.profile.handle) return activeTracks?.length === 0 ? <> : ( @@ -27,7 +29,11 @@ const MemberStats: FC = props => {

    {activeTracks.map((track: any) => ( -
  • + {track.name}
    {!track.ranking && ((track.submissions || track.wins) > 0) && ( @@ -43,6 +49,7 @@ const MemberStats: FC = props => { )} + {/* competitive programming only */} {track.ranking !== undefined && ( @@ -58,7 +65,7 @@ const MemberStats: FC = props => { className={classNames('icon-lg', styles.rightArrowIcon)} />
    -
  • + ))}

@@ -73,4 +80,4 @@ const MemberStats: FC = props => { ) } -export default MemberStats +export default MemberStatsBlock diff --git a/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/index.ts b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/index.ts new file mode 100644 index 000000000..1ff7f5737 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/index.ts @@ -0,0 +1 @@ +export { default as MemberStatsBlock } from './MemberStatsBlock' diff --git a/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.module.scss b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.module.scss new file mode 100644 index 000000000..c69d43bb5 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.module.scss @@ -0,0 +1,23 @@ +@import '@libs/ui/styles/includes'; + +.wrap { + margin-top: -1*$sp-2; + padding-bottom: $sp-6; + + > hr { + margin: $sp-6 0; + } +} + +.headline { + font-family: $font-roboto; + font-size: 20px; + line-height: 24px; + color: $black-80; + + margin-top: $sp-4; +} + +.summaryBlock { + margin-bottom: $sp-6; +} diff --git a/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx new file mode 100644 index 000000000..34f7a321f --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx @@ -0,0 +1,42 @@ +import { FC, PropsWithChildren } from 'react' +import { To } from 'react-router-dom' + +import { StatsNavHeader } from '../StatsNavHeader' +import { StatsSummaryBlock } from '../StatsSummaryBlock' +import { MemberStatsTrack } from '../../../hooks/useFetchActiveTracks' + +import styles from './StatsDetailsLayout.module.scss' + +interface StatsDetailsLayoutProps extends PropsWithChildren { + title: string + prevTitle: string + backAction: To + closeAction: To + trackData: MemberStatsTrack +} + +const StatsDetailsLayout: FC = props => ( +

+ +
+ {props.title} +
+
+
+ +
+ {props.children} +
+) + +export default StatsDetailsLayout diff --git a/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/index.ts b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/index.ts new file mode 100644 index 000000000..3fc513ab8 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/index.ts @@ -0,0 +1 @@ +export { default as StatsDetailsLayout } from './StatsDetailsLayout' diff --git a/src/apps/profiles/src/components/tc-achievements/StatsNavHeader/StatsNavHeader.module.scss b/src/apps/profiles/src/components/tc-achievements/StatsNavHeader/StatsNavHeader.module.scss new file mode 100644 index 000000000..970b89d06 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/StatsNavHeader/StatsNavHeader.module.scss @@ -0,0 +1,7 @@ +@import '@libs/ui/styles/includes'; + +.wrap { + display: flex; + align-items: center; + justify-content: space-between; +} diff --git a/src/apps/profiles/src/components/tc-achievements/StatsNavHeader/StatsNavHeader.tsx b/src/apps/profiles/src/components/tc-achievements/StatsNavHeader/StatsNavHeader.tsx new file mode 100644 index 000000000..99255a6e5 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/StatsNavHeader/StatsNavHeader.tsx @@ -0,0 +1,34 @@ +import { FC } from 'react' +import { To } from 'react-router-dom' + +import { IconOutline, LinkButton } from '~/libs/ui' + +import styles from './StatsNavHeader.module.scss' + +interface StatsNavHeaderProps { + backLabel: string + backAction: To + closeAction: To +} + +const StatsNavHeader: FC = props => ( +
+ + + +
+) + +export default StatsNavHeader diff --git a/src/apps/profiles/src/components/tc-achievements/StatsNavHeader/index.ts b/src/apps/profiles/src/components/tc-achievements/StatsNavHeader/index.ts new file mode 100644 index 000000000..b5098250c --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/StatsNavHeader/index.ts @@ -0,0 +1 @@ +export { default as StatsNavHeader } from './StatsNavHeader' diff --git a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.module.scss b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.module.scss new file mode 100644 index 000000000..46cb89cdd --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.module.scss @@ -0,0 +1,36 @@ +@import '@libs/ui/styles/includes'; + +.wrap { + background: $black-5; + padding: $sp-4; + border-radius: 6px; + width: 100%; +} + +.title { + margin-bottom: $sp-3; +} + +.summary { + display: flex; + align-items: center; + gap: $sp-12; +} + +.summaryItem { + display: flex; + align-items: flex-end; + gap: $sp-2; + + &Value { + font-family: $font-barlow-condensed; + font-weight: $font-weight-medium; + font-size: 32px; + line-height: 34px; + color: $turq-160; + } + + &Label { + line-height: 23px; + } +} diff --git a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx new file mode 100644 index 000000000..3da716b8e --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx @@ -0,0 +1,71 @@ +import { FC } from 'react' + +import styles from './StatsSummaryBlock.module.scss' + +interface StatsSummaryBlockProps { + trackTitle: string + challenges?: number + wins?: number + submissions?: number + ranking?: number +} + +const StatsSummaryBlock: FC = props => ( +
+
+ + {props.trackTitle} +  Stats + +
+
+
+ + {props.challenges} + + + + Challenges + + +
+
+ + {props.wins} + + + + Wins + + +
+ {props.submissions !== undefined && ( +
+ + {props.submissions} + + + + Submissions + + +
+ )} + {props.ranking !== undefined && ( +
+ + {props.ranking} + % + + + + Percentile + + +
+ )} +
+
+) + +export default StatsSummaryBlock diff --git a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/index.ts b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/index.ts new file mode 100644 index 000000000..8b5ba3a21 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/index.ts @@ -0,0 +1 @@ +export { default as StatsSummaryBlock } from './StatsSummaryBlock' diff --git a/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.module.scss b/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.module.scss new file mode 100644 index 000000000..36ac70714 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.module.scss @@ -0,0 +1,39 @@ +@import '@libs/ui/styles/includes'; + +.wrap { + border: 1px solid $black-20; + border-radius: $sp-3; + padding: $sp-4; +} + +.title { + margin-bottom: $sp-2; +} + +.stats { + display: flex; + align-items: center; + gap: $sp-4; +} + +.statsItem { + display: flex; + align-items: center; + gap: $sp-1; + + &Value { + color: $turq-160; + font-family: $font-barlow-condensed; + font-weight: $font-weight-medium; + font-size: 26px; + line-height: 34px; + } + + &Label { + color: $black-80; + } + + &Icon { + margin-left: auto; + } +} diff --git a/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.tsx b/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.tsx new file mode 100644 index 000000000..26629a468 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.tsx @@ -0,0 +1,48 @@ +import { FC } from 'react' +import classNames from 'classnames' + +import { IconSolid } from '~/libs/ui' + +import { subTrackLabelToHumanName, WinnerIcon } from '../../../lib' + +import styles from './SubTrackSummaryCard.module.scss' + +interface SubTrackSummaryCardProps { + submissions: number + title: string + wins: number +} + +const SubTrackSummaryCard: FC = props => ( +
+
+ + {subTrackLabelToHumanName(props.title)} + +
+
+
+ + + {props.wins} + + + wins + +
+
+ + {props.submissions} + + + submissions + +
+
+ +
+
+
+) + +export default SubTrackSummaryCard diff --git a/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/index.ts b/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/index.ts new file mode 100644 index 000000000..7e58e65e0 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/index.ts @@ -0,0 +1 @@ +export { default as SubTrackSummaryCard } from './SubTrackSummaryCard' diff --git a/src/apps/profiles/src/member-profile/tc-achievements/TCOWinsBanner/TCOWinsBanner.module.scss b/src/apps/profiles/src/components/tc-achievements/TCOWinsBanner/TCOWinsBanner.module.scss similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/TCOWinsBanner/TCOWinsBanner.module.scss rename to src/apps/profiles/src/components/tc-achievements/TCOWinsBanner/TCOWinsBanner.module.scss diff --git a/src/apps/profiles/src/member-profile/tc-achievements/TCOWinsBanner/TCOWinsBanner.tsx b/src/apps/profiles/src/components/tc-achievements/TCOWinsBanner/TCOWinsBanner.tsx similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/TCOWinsBanner/TCOWinsBanner.tsx rename to src/apps/profiles/src/components/tc-achievements/TCOWinsBanner/TCOWinsBanner.tsx diff --git a/src/apps/profiles/src/member-profile/tc-achievements/TCOWinsBanner/assets/tco-logo-banner.png b/src/apps/profiles/src/components/tc-achievements/TCOWinsBanner/assets/tco-logo-banner.png similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/TCOWinsBanner/assets/tco-logo-banner.png rename to src/apps/profiles/src/components/tc-achievements/TCOWinsBanner/assets/tco-logo-banner.png diff --git a/src/apps/profiles/src/member-profile/tc-achievements/TCOWinsBanner/index.ts b/src/apps/profiles/src/components/tc-achievements/TCOWinsBanner/index.ts similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/TCOWinsBanner/index.ts rename to src/apps/profiles/src/components/tc-achievements/TCOWinsBanner/index.ts diff --git a/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/MemberRolesInfoModal.tsx b/src/apps/profiles/src/components/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/MemberRolesInfoModal.tsx similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/MemberRolesInfoModal.tsx rename to src/apps/profiles/src/components/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/MemberRolesInfoModal.tsx diff --git a/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/index.ts b/src/apps/profiles/src/components/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/index.ts similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/index.ts rename to src/apps/profiles/src/components/tc-achievements/TcSpecialRolesBanner/MemberRolesInfoModal/index.ts diff --git a/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.module.scss b/src/apps/profiles/src/components/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.module.scss similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.module.scss rename to src/apps/profiles/src/components/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.module.scss diff --git a/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.tsx b/src/apps/profiles/src/components/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.tsx similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.tsx rename to src/apps/profiles/src/components/tc-achievements/TcSpecialRolesBanner/TcSpecialRolesBanner.tsx diff --git a/src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/index.ts b/src/apps/profiles/src/components/tc-achievements/TcSpecialRolesBanner/index.ts similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/TcSpecialRolesBanner/index.ts rename to src/apps/profiles/src/components/tc-achievements/TcSpecialRolesBanner/index.ts diff --git a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx index d103148d3..7a02430c1 100644 --- a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx +++ b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx @@ -1,11 +1,50 @@ -import { filter, orderBy } from 'lodash' import { useMemo } from 'react' +import { filter, find, orderBy } from 'lodash' + +import { MemberStats, SRMStats, useMemberStats, UserStats } from '~/libs/core' + +export interface MemberStatsTrack { + challenges?: number, + isActive: boolean, + name: string, + submissions?: number, + subTracks: MemberStats[], + ranking?: number, + wins: number, +} -import { MemberStats, useMemberStats, UserStats } from '~/libs/core' +const buildTrackData = (trackName: string, subTracks: MemberStats[]): MemberStatsTrack => { + const totalWins = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.wins || 0)), 0) + const challengesCount = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.challenges || 0)), 0) + const submissionsCount = subTracks.reduce((sum, subTrack) => ( + sum + (subTrack?.submissions?.submissions || 0) + ), 0) + + return { + challenges: challengesCount, + isActive: challengesCount > 0, + name: trackName, + submissions: submissionsCount, + subTracks, + wins: totalWins, + } +} -export const useFetchActiveTracks = (userHandle: string): any => { +export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => { const memberStats: UserStats | undefined = useMemberStats(userHandle) + const dataScienceSubTracks: {[key: string]: MemberStats | SRMStats} = useMemo(() => ({ + MARATHON_MATCH: (memberStats?.DATA_SCIENCE?.MARATHON_MATCH && ({ + ...memberStats.DATA_SCIENCE.MARATHON_MATCH, + name: 'MARATHON_MATCH', + })) as MemberStats, + SRM: (memberStats?.DATA_SCIENCE?.SRM && ({ + ...memberStats.DATA_SCIENCE.SRM, + name: 'SRM', + })) as SRMStats & {name: string}, + }), [memberStats]) + + // Create mappings for the subtracks, by the subtrack name, so we can easily access it later on const designSubTracks: {[key: string]: MemberStats} = useMemo(() => ( memberStats?.DESIGN?.subTracks.reduce((all, subTrack) => { all[subTrack.name] = subTrack @@ -21,115 +60,62 @@ export const useFetchActiveTracks = (userHandle: string): any => { ), [memberStats]) // Design - const designTrackStats = useMemo(() => { - const designStats = developSubTracks.DESIGN - const designF2FStats = designSubTracks.DESIGN_FIRST_2_FINISH - const webDesignStats = designSubTracks.WEB_DESIGNS - const logoDesignStats = designSubTracks.LOGO_DESIGN - const wireframesStats = designSubTracks.WIREFRAMES - const frontEndFlashStats = designSubTracks.FRONT_END_FLASH - const printPresentationStats = designSubTracks.PRINT_OR_PRESENTATION - const studioOtherStats = designSubTracks.STUDIO_OTHER - const feDesignStats = designSubTracks.APPLICATION_FRONT_END_DESIGN - const bannersIconsStats = designSubTracks.BANNERS_OR_ICONS - const widgetMobileStats = designSubTracks.WIDGET_OR_MOBILE_SCREEN_DESIGN - - const subTracks = [ - designStats, - designF2FStats, - webDesignStats, - logoDesignStats, - wireframesStats, - frontEndFlashStats, - printPresentationStats, - studioOtherStats, - feDesignStats, - bannersIconsStats, - widgetMobileStats, - ] - - const totalWins = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.wins || 0)), 0) - const challengesCount = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.challenges || 0)), 0) - const submissionsCount = subTracks.reduce((sum, subTrack) => ( - sum + (subTrack?.submissions?.submissions || 0) - ), 0) - - return { - isActive: challengesCount > 0, - name: 'Design', - submissions: submissionsCount, - wins: totalWins, - } - }, [developSubTracks, designSubTracks]) + const designTrackStats: MemberStatsTrack = useMemo(() => ( + buildTrackData('Design', [ + developSubTracks.DESIGN, + designSubTracks.DESIGN_FIRST_2_FINISH, + designSubTracks.WEB_DESIGNS, + designSubTracks.LOGO_DESIGN, + designSubTracks.WIREFRAMES, + designSubTracks.FRONT_END_FLASH, + designSubTracks.PRINT_OR_PRESENTATION, + designSubTracks.STUDIO_OTHER, + designSubTracks.APPLICATION_FRONT_END_DESIGN, + designSubTracks.BANNERS_OR_ICONS, + designSubTracks.WIDGET_OR_MOBILE_SCREEN_DESIGN, + ].filter(Boolean)) + ), [developSubTracks, designSubTracks]) // Development - const developTrackStats = useMemo(() => { - const developmentStats = developSubTracks.DEVELOPMENT - const architectureStats = developSubTracks.ARCHITECTURE - const f2fStats = developSubTracks.FIRST_2_FINISH - const codeStats = developSubTracks.CODE - const assemblyStats = developSubTracks.ASSEMBLY_COMPETITION - const uiPrototypeStats = developSubTracks.UI_PROTOTYPE_COMPETITION - - const subTracks = [ - developmentStats, - architectureStats, - f2fStats, - codeStats, - assemblyStats, - uiPrototypeStats, - ] - - const totalWins = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.wins || 0)), 0) - const challengesCount = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.challenges || 0)), 0) - const submissionsCount = subTracks.reduce((sum, subTrack) => ( - sum + (subTrack?.submissions?.submissions || 0) - ), 0) - - return { - isActive: challengesCount > 0, - name: 'Development', - submissions: submissionsCount, - wins: totalWins, - } - }, [developSubTracks]) + const developTrackStats: MemberStatsTrack = useMemo(() => ( + buildTrackData('Development', [ + developSubTracks.DEVELOPMENT, + developSubTracks.ARCHITECTURE, + developSubTracks.FIRST_2_FINISH, + developSubTracks.CODE, + developSubTracks.ASSEMBLY_COMPETITION, + developSubTracks.UI_PROTOTYPE_COMPETITION, + ].filter(Boolean)) + ), [developSubTracks]) // Testing - const testingTrackStats = useMemo(() => { - const bugHuntStats = developSubTracks.BUG_HUNT - const testScenStats = developSubTracks.TEST_SCENARIOS - const testSuitesStats = developSubTracks.TEST_SUITES + const testingTrackStats: MemberStatsTrack = useMemo(() => ( + buildTrackData('Testing', [ + developSubTracks.BUG_HUNT, + developSubTracks.TEST_SCENARIOS, + developSubTracks.TEST_SUITES, + ].filter(Boolean)) + ), [developSubTracks]) + // Competitive Programming + const cpTrackStats: MemberStatsTrack = useMemo(() => { const subTracks = [ - bugHuntStats, - testScenStats, - testSuitesStats, - ] - - const totalWins = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.wins || 0)), 0) - const challengesCount = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.challenges || 0)), 0) - const submissionsCount = subTracks.reduce((sum, subTrack) => ( - sum + (subTrack?.submissions?.submissions || 0) - ), 0) + dataScienceSubTracks.MARATHON_MATCH, + dataScienceSubTracks.SRM, + ].filter(Boolean) as MemberStats[] return { - isActive: challengesCount > 0, - name: 'Testing', - submissions: submissionsCount, - wins: totalWins, + challenges: memberStats?.DATA_SCIENCE?.challenges ?? 0, + isActive: (memberStats?.DATA_SCIENCE?.challenges ?? 0) > 0, + name: 'Competitive Programming', + ranking: Math.max( + dataScienceSubTracks.MARATHON_MATCH?.rank.percentile ?? 0, + dataScienceSubTracks.SRM?.rank.percentile ?? 0, + ), + subTracks, + wins: memberStats?.DATA_SCIENCE?.wins ?? 0, } - }, [developSubTracks]) - - // Competitive Programming - const cpTrackStats = useMemo(() => ({ - isActive: (memberStats?.DATA_SCIENCE?.challenges ?? 0) > 0, - name: 'Competitive Programming', - ranking: Math.max( - memberStats?.DATA_SCIENCE?.MARATHON_MATCH.rank.percentile ?? 0, - memberStats?.DATA_SCIENCE?.SRM.rank.percentile ?? 0, - ), - wins: memberStats?.DATA_SCIENCE?.wins ?? 0, - }), [memberStats]) + }, [dataScienceSubTracks, memberStats]) // copilot // const copilotTrackStats = useMemo(() => ({ @@ -148,3 +134,22 @@ export const useFetchActiveTracks = (userHandle: string): any => { testingTrackStats, ], { isActive: true }), ['wins', 'submissions'], 'desc') } + +export const useFetchTrackData = (userHandle: string, track: string | undefined): any => { + const activeTracks = useFetchActiveTracks(userHandle) + return find(activeTracks, { name: track }) +} + +export const useFetchSubTrackData = ( + userHandle: string, + track: string | undefined, + subTrack: string | undefined, +): any => { + const activeTracks = useFetchActiveTracks(userHandle) + const trackData = find(activeTracks, { name: track }) + const subTrackData = find(trackData?.subTracks, { name: subTrack }) + + return { + ...subTrackData, trackData, + } +} diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/winner-icon.svg b/src/apps/profiles/src/lib/assets/winner-icon.svg similarity index 100% rename from src/apps/profiles/src/member-profile/tc-achievements/MemberStats/winner-icon.svg rename to src/apps/profiles/src/lib/assets/winner-icon.svg diff --git a/src/apps/profiles/src/lib/helpers.ts b/src/apps/profiles/src/lib/helpers.ts index ce56d9798..b80c17c5a 100644 --- a/src/apps/profiles/src/lib/helpers.ts +++ b/src/apps/profiles/src/lib/helpers.ts @@ -51,6 +51,10 @@ export function subTrackLabelToHumanName(label: string): string { return 'Web Design' case 'WIREFRAMES': return 'Wireframes' + case 'MARATHON_MATCH': + return 'Marathon Match' + case 'SRM': + return 'Single Round Match' case 'FRONT_END_FLASH': return 'Front End Flash' case 'PRINT_OR_PRESENTATION': diff --git a/src/apps/profiles/src/lib/index.ts b/src/apps/profiles/src/lib/index.ts index 419187c80..dfd26ed29 100644 --- a/src/apps/profiles/src/lib/index.ts +++ b/src/apps/profiles/src/lib/index.ts @@ -1,2 +1,3 @@ export * from './helpers' export * from './userflow-survey' +export { ReactComponent as WinnerIcon } from './assets/winner-icon.svg' diff --git a/src/apps/profiles/src/member-profile/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx b/src/apps/profiles/src/member-profile/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx deleted file mode 100644 index 069797d98..000000000 --- a/src/apps/profiles/src/member-profile/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx +++ /dev/null @@ -1,531 +0,0 @@ -/* eslint-disable complexity */ -import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react' -import { bind } from 'lodash' - -import { MemberStats, UserProfile, UserStats } from '~/libs/core' - -import { subTrackLabelToHumanName } from '../../../lib/helpers' -import { - AssemblyDetailsModal, - BugHuntDetailsModal, - CodeDetailsModal, - ContentCreationDetailsModal, - CopilotDetailsModal, - DesignF2FDetailsModal, - F2FDetailsModal, - GenericSubtrackDetailsModal, - LogoDesignDetailsModal, - MMDetailsModal, - SRMDetailsModal, - TestScenariosDetailsModal, - UIPrototypeDetailsModal, - WebDesignDetailsModal, -} from '../../../components' - -import { ChallengeWin } from './ChallengeWin' -import styles from './ChallengeWinsBanner.module.scss' - -interface ChallengeWinsBannerProps { - memberStats: UserStats - profile: UserProfile -} - -const ChallengeWinsBanner: FC = (props: ChallengeWinsBannerProps) => { - const f2fStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'FIRST_2_FINISH'), - [props.memberStats], - ) - const codeStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'CODE'), - [props.memberStats], - ) - const assemblyStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'ASSEMBLY_COMPETITION'), - [props.memberStats], - ) - const contentCreationStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'CONTENT_CREATION'), - [props.memberStats], - ) - const uiPrototypeStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'UI_PROTOTYPE_COMPETITION'), - [props.memberStats], - ) - const designF2FStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DESIGN?.subTracks.find(subTrack => subTrack.name === 'DESIGN_FIRST_2_FINISH'), - [props.memberStats], - ) - const webDesignStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DESIGN?.subTracks.find(subTrack => subTrack.name === 'WEB_DESIGNS'), - [props.memberStats], - ) - const logoDesignStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DESIGN?.subTracks.find(subTrack => subTrack.name === 'LOGO_DESIGN'), - [props.memberStats], - ) - const bugHuntStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks?.find(subTrack => subTrack.name === 'BUG_HUNT'), - [props.memberStats], - ) - const testScenStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks?.find(subTrack => subTrack.name === 'TEST_SCENARIOS'), - [props.memberStats], - ) - const wireframesStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DESIGN?.subTracks.find(subTrack => subTrack.name === 'WIREFRAMES'), - [props.memberStats], - ) - const frontEndFlashStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DESIGN?.subTracks.find(subTrack => subTrack.name === 'FRONT_END_FLASH'), - [props.memberStats], - ) - const printPresentationStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DESIGN?.subTracks.find(subTrack => subTrack.name === 'PRINT_OR_PRESENTATION'), - [props.memberStats], - ) - const studioOtherStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DESIGN?.subTracks.find(subTrack => subTrack.name === 'STUDIO_OTHER'), - [props.memberStats], - ) - const feDesignStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DESIGN?.subTracks - .find(subTrack => subTrack.name === 'APPLICATION_FRONT_END_DESIGN'), - [props.memberStats], - ) - const bannersIconsStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DESIGN?.subTracks.find(subTrack => subTrack.name === 'BANNERS_OR_ICONS'), - [props.memberStats], - ) - const widgetMobileStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DESIGN?.subTracks - .find(subTrack => subTrack.name === 'WIDGET_OR_MOBILE_SCREEN_DESIGN'), - [props.memberStats], - ) - const testSuitesStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'TEST_SUITES'), - [props.memberStats], - ) - const specStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'SPECIFICATION'), - [props.memberStats], - ) - const developmentStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'DEVELOPMENT'), - [props.memberStats], - ) - const architectureStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'ARCHITECTURE'), - [props.memberStats], - ) - const copilotPostingStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'COPILOT_POSTING'), - [props.memberStats], - ) - const designStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'DESIGN'), - [props.memberStats], - ) - const conceptStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'CONCEPTUALIZATION'), - [props.memberStats], - ) - - const [modalVisibilityMap, setModalVisibilityMap]: [ - { [key: string]: boolean }, - Dispatch> - ] = useState<{ [key: string]: boolean }>({ - APPLICATION_FRONT_END_DESIGN: false, - ARCHITECTURE: false, - ASSEMBLY_COMPETITION: false, - BANNERS_OR_ICONS: false, - BUG_HUNT: false, - CODE: false, - CONCEPTUALIZATION: false, - CONTENT_CREATION: false, - COPILOT: false, - COPILOT_POSTING: false, - DESIGN: false, - DESIGN_FIRST_2_FINISH: false, - DEVELOPMENT: false, - DS: false, - FIRST_2_FINISH: false, - FRONT_END_FLASH: false, - LOGO_DESIGN: false, - PRINT_OR_PRESENTATION: false, - SPECIFICATION: false, - SRM: false, - STUDIO_OTHER: false, - TEST_SCENARIOS: false, - TEST_SUITES: false, - UI_PROTOTYPE_COMPETITION: false, - WEB_DESIGNS: false, - WIDGET_OR_MOBILE_SCREEN_DESIGN: false, - WIREFRAMES: false, - }) - - function handleChallengeWinModalToggle(subTrack: string): void { - setModalVisibilityMap({ - ...modalVisibilityMap, - [subTrack]: !modalVisibilityMap[subTrack], - }) - } - - return ( -
-
-
-

Topcoder Challenge Winner

-
- { - !!props.memberStats.DATA_SCIENCE?.SRM?.wins && ( - - ) - } - { - !!props.memberStats.DATA_SCIENCE?.MARATHON_MATCH?.wins && ( - - ) - } - { - !!props.memberStats.DEVELOP?.wins - && props.memberStats.DEVELOP?.subTracks.map((ms: MemberStats) => (ms.wins ? ( - - ) : undefined)) - } - { - !!props.memberStats.DESIGN?.wins - && props.memberStats.DESIGN?.subTracks.map((ms: MemberStats) => (ms.wins ? ( - - ) : undefined)) - } - { - !!props.memberStats.COPILOT && ( - - ) - } -
-

- Topcoder challenges are open competitions where community - members participate in small units of work to deliver projects. -

-
-
- - {modalVisibilityMap.SRM && ( - - )} - - {modalVisibilityMap.DS && ( - - )} - - {modalVisibilityMap.CODE && ( - - )} - - {modalVisibilityMap.FIRST_2_FINISH && ( - - )} - - {modalVisibilityMap.ASSEMBLY_COMPETITION && ( - - )} - - {modalVisibilityMap.CONTENT_CREATION && ( - - )} - - {modalVisibilityMap.UI_PROTOTYPE_COMPETITION && ( - - )} - - {modalVisibilityMap.WEB_DESIGNS && ( - - )} - - {modalVisibilityMap.LOGO_DESIGN && ( - - )} - - {modalVisibilityMap.DESIGN_FIRST_2_FINISH && ( - - )} - - {modalVisibilityMap.TEST_SCENARIOS && ( - - )} - - {modalVisibilityMap.BUG_HUNT && ( - - )} - - {modalVisibilityMap.COPILOT && ( - - )} - - {modalVisibilityMap.WIREFRAMES && ( - - )} - - {modalVisibilityMap.FRONT_END_FLASH && ( - - )} - - {modalVisibilityMap.PRINT_OR_PRESENTATION && ( - - )} - - {modalVisibilityMap.STUDIO_OTHER && ( - - )} - - {modalVisibilityMap.APPLICATION_FRONT_END_DESIGN && ( - - )} - - {modalVisibilityMap.BANNERS_OR_ICONS && ( - - )} - - {modalVisibilityMap.WIDGET_OR_MOBILE_SCREEN_DESIGN && ( - - )} - - {modalVisibilityMap.TEST_SUITES && ( - - )} - - {modalVisibilityMap.SPECIFICATION && ( - - )} - - {modalVisibilityMap.DEVELOPMENT && ( - - )} - - {modalVisibilityMap.ARCHITECTURE && ( - - )} - - {modalVisibilityMap.COPILOT_POSTING && ( - - )} - - {modalVisibilityMap.DESIGN && ( - - )} - - {modalVisibilityMap.CONCEPTUALIZATION && ( - - )} - -
- ) -} - -export default ChallengeWinsBanner diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/index.ts b/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/index.ts deleted file mode 100644 index c18662ca6..000000000 --- a/src/apps/profiles/src/member-profile/tc-achievements/MemberStats/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as MemberStats } from './MemberStats' diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.module.scss b/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.module.scss index 1b89e9d06..0a0c18e7f 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.module.scss +++ b/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.module.scss @@ -15,24 +15,4 @@ >p { text-align: left; } - - .achievementsWrap { - display: flex; - margin: $sp-4 0 $sp-2; - align-items: stretch; - - @include ltelg { - flex-direction: column; - - } - - >div:not(:last-child) { - margin-right: $sp-8; - - @include ltelg { - margin-right: 0; - margin-bottom: $sp-8; - } - } - } } diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx b/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx index 3d2937fc9..2796e300c 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx +++ b/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx @@ -1,4 +1,5 @@ import { FC, useMemo } from 'react' +import { Outlet, Route, Routes } from 'react-router-dom' import { useMemberBadges, @@ -9,11 +10,9 @@ import { UserStats, } from '~/libs/core' -import { CommunityAwards } from '../community-awards' - -import { TCOWinsBanner } from './TCOWinsBanner' -import { TcSpecialRolesBanner } from './TcSpecialRolesBanner' -import { MemberStats } from './MemberStats' +import { DefaultAchievementsView } from './default-achievements-view' +import { TrackView } from './track-view' +import { SubTrackView } from './sub-track-view' import styles from './MemberTCAchievements.module.scss' interface MemberTCAchievementsProps { @@ -38,24 +37,41 @@ const MemberTCAchievements: FC = (props: MemberTCAchi (badge: UserBadge) => /TCO.*Trip Winner/.test(badge.org_badge.badge_name), ).length || 0, [memberBadges]) - return memberStats?.wins || tcoWins || tcoQualifications ? ( -
-

Achievements @ Topcoder

- -
- { - (tcoWins > 0 || tcoQualifications > 0 || tcoTrips > 0) && ( - - ) - } - -
+ if (!memberStats?.wins && !tcoWins && !tcoQualifications) { + return <> + } - - - + return ( +
+ + + + )} + /> + + )} + /> + + )} + /> +
- ) : <> + ) } export default MemberTCAchievements diff --git a/src/apps/profiles/src/member-profile/tc-achievements/default-achievements-view/DefaultAchievementsView.module.scss b/src/apps/profiles/src/member-profile/tc-achievements/default-achievements-view/DefaultAchievementsView.module.scss new file mode 100644 index 000000000..823ece5e8 --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/default-achievements-view/DefaultAchievementsView.module.scss @@ -0,0 +1,21 @@ +@import '@libs/ui/styles/includes'; + +.achievementsWrap { + display: flex; + margin: $sp-4 0 $sp-2; + align-items: stretch; + + @include ltelg { + flex-direction: column; + + } + + >div:not(:last-child) { + margin-right: $sp-8; + + @include ltelg { + margin-right: 0; + margin-bottom: $sp-8; + } + } +} diff --git a/src/apps/profiles/src/member-profile/tc-achievements/default-achievements-view/DefaultAchievementsView.tsx b/src/apps/profiles/src/member-profile/tc-achievements/default-achievements-view/DefaultAchievementsView.tsx new file mode 100644 index 000000000..dba2442a4 --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/default-achievements-view/DefaultAchievementsView.tsx @@ -0,0 +1,41 @@ +import { FC } from 'react' + +import { UserProfile, UserStats } from '~/libs/core' + +import { CommunityAwards } from '../../community-awards' +import { MemberStatsBlock } from '../../../components/tc-achievements/MemberStatsBlock' +import { TcSpecialRolesBanner } from '../../../components/tc-achievements/TcSpecialRolesBanner' +import { TCOWinsBanner } from '../../../components/tc-achievements/TCOWinsBanner' + +import styles from './DefaultAchievementsView.module.scss' + +interface DefaultAchievementsViewProps { + memberStats: UserStats | undefined + profile: UserProfile + tcoWins: number + tcoQualifications: number + tcoTrips: number +} + +const DefaultAchievementsView: FC = props => ( + <> +

Achievements @ Topcoder

+ +
+ {(props.tcoWins > 0 || props.tcoQualifications > 0 || props.tcoTrips > 0) && ( + + )} + +
+ + + + + +) + +export default DefaultAchievementsView diff --git a/src/apps/profiles/src/member-profile/tc-achievements/default-achievements-view/index.ts b/src/apps/profiles/src/member-profile/tc-achievements/default-achievements-view/index.ts new file mode 100644 index 000000000..7c72469c6 --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/default-achievements-view/index.ts @@ -0,0 +1 @@ +export { default as DefaultAchievementsView } from './DefaultAchievementsView' diff --git a/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.module.scss b/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.module.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx b/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx new file mode 100644 index 000000000..73d718ee4 --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx @@ -0,0 +1,44 @@ +import { FC } from 'react' +import { useParams } from 'react-router-dom' + +import { UserProfile } from '~/libs/core' + +import { useFetchSubTrackData } from '../../../hooks' +import { StatsDetailsLayout } from '../../../components/tc-achievements/StatsDetailsLayout' +import { getUserProfileRoute, getUserProfileStatsRoute } from '../../../profiles.routes' +import { subTrackLabelToHumanName } from '../../../lib' + +import styles from './SubTrackView.module.scss' + +interface SubTrackViewProps { + profile: UserProfile +} + +const SubTrackView: FC = props => { + const params = useParams() + const { trackData, ...subTrackData }: any + = useFetchSubTrackData(props.profile.handle, params.trackType, params.subTrack) + + return ( +
+ + {subTrackData.name === 'MARATHON_MATCH' || subTrackData.name === 'SRM' ? ( + 'SRM TEst' + ) : subTrackData.name === 'WEB_DESIGNS' ? ( + 'WEB_DESIGNS test' + ) : ( + 'Other test' + )} + test + +
+ ) +} + +export default SubTrackView diff --git a/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/index.ts b/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/index.ts new file mode 100644 index 000000000..20074c3a2 --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/index.ts @@ -0,0 +1 @@ +export { default as SubTrackView } from './SubTrackView' diff --git a/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.module.scss b/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.module.scss new file mode 100644 index 000000000..bb76b05a5 --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.module.scss @@ -0,0 +1,12 @@ +@import '@libs/ui/styles/includes'; + +.cardsWrap { + display: flex; + flex-wrap: wrap; + gap: $sp-6; + + > * { + display: block; + width: calc(33% - 14px); + } +} diff --git a/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx b/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx new file mode 100644 index 000000000..671f32235 --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx @@ -0,0 +1,51 @@ +import { FC } from 'react' +import { Link, useParams } from 'react-router-dom' + +import { MemberStats, UserProfile } from '~/libs/core' + +import { useFetchTrackData } from '../../../hooks' +import { getUserProfileRoute, getUserProfileStatsRoute } from '../../../profiles.routes' +import { StatsDetailsLayout } from '../../../components/tc-achievements/StatsDetailsLayout' +import { SubTrackSummaryCard } from '../../../components/tc-achievements/SubTrackSummaryCard' + +import styles from './TrackView.module.scss' + +interface TrackViewProps { + profile: UserProfile +} + +const TrackView: FC = props => { + const params = useParams() + const trackData = useFetchTrackData(props.profile.handle, params.trackType) + console.log('here', trackData) + + return ( +
+ +
+ {trackData.subTracks.map((subTrack: MemberStats) => ( + + + + ))} +
+
+
+ ) +} + +export default TrackView diff --git a/src/apps/profiles/src/member-profile/tc-achievements/track-view/index.ts b/src/apps/profiles/src/member-profile/tc-achievements/track-view/index.ts new file mode 100644 index 000000000..7cbd1fe99 --- /dev/null +++ b/src/apps/profiles/src/member-profile/tc-achievements/track-view/index.ts @@ -0,0 +1 @@ +export { default as TrackView } from './TrackView' diff --git a/src/apps/profiles/src/profiles.routes.tsx b/src/apps/profiles/src/profiles.routes.tsx index eee6288b0..247e69973 100644 --- a/src/apps/profiles/src/profiles.routes.tsx +++ b/src/apps/profiles/src/profiles.routes.tsx @@ -14,6 +14,14 @@ export const rootRoute: string = ( export const toolTitle: string = ToolTitle.profiles export const absoluteRootRoute: string = `${window.location.origin}${rootRoute}` +export const getUserProfileRoute = (userHandle?: string): string => ( + `${rootRoute}${!userHandle ? '' : `/${userHandle.toLowerCase()}`}` +) + +export const getUserProfileStatsRoute = (userHandle: string, track: string, subTrack?: string): string => ( + `${getUserProfileRoute(userHandle)}/stats/${track}${!subTrack ? '' : `/${subTrack}`}` +) + export const profilesRoutes: ReadonlyArray = [ { children: [ @@ -27,6 +35,11 @@ export const profilesRoutes: ReadonlyArray = [ id: 'MemberProfilePage', route: ':memberHandle', }, + { + element: , + id: 'MemberProfilePageSub', + route: ':memberHandle/stats/*', + }, { element: , id: 'MemberBadgesPage', From ef67b54090f47395a053a622c8a922c12851e4bb Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Nov 2023 10:33:52 +0200 Subject: [PATCH 11/37] TAL-93 - update member profiles to use member.availableForGigs --- .../OpenForGigs/OpenForGigs.tsx | 26 ++++----------- .../OpenForGigsModifyModal.tsx | 32 +++---------------- .../lib/profile/modify-user-profile.model.ts | 1 + .../core/lib/profile/user-profile.model.ts | 1 + 4 files changed, 14 insertions(+), 46 deletions(-) diff --git a/src/apps/profiles/src/member-profile/profile-header/OpenForGigs/OpenForGigs.tsx b/src/apps/profiles/src/member-profile/profile-header/OpenForGigs/OpenForGigs.tsx index d3e70563d..a7092513c 100644 --- a/src/apps/profiles/src/member-profile/profile-header/OpenForGigs/OpenForGigs.tsx +++ b/src/apps/profiles/src/member-profile/profile-header/OpenForGigs/OpenForGigs.tsx @@ -1,9 +1,8 @@ -import { Dispatch, FC, SetStateAction, useEffect, useMemo, useState } from 'react' +import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react' import { useSearchParams } from 'react-router-dom' -import { KeyedMutator } from 'swr' import classNames from 'classnames' -import { useMemberTraits, UserProfile, UserTrait, UserTraitIds, UserTraits } from '~/libs/core' +import { UserProfile } from '~/libs/core' import { EditMemberPropertyBtn } from '../../../components' import { OpenForGigsModifyModal } from '../OpenForGigsModifyModal' @@ -26,6 +25,8 @@ const OpenForGigs: FC = (props: OpenForGigsProps) => { const [isEditMode, setIsEditMode]: [boolean, Dispatch>] = useState(false) + const openForWork = props.profile.availableForGigs + useEffect(() => { if (props.authProfile && editMode === profileEditModes.openForWork) { setIsEditMode(true) @@ -33,17 +34,6 @@ const OpenForGigs: FC = (props: OpenForGigsProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.authProfile]) - const { data: memberPersonalizationTraits, mutate: mutateTraits }: { - data: UserTraits[] | undefined, - mutate: KeyedMutator, - } - = useMemberTraits(props.profile.handle, { traitIds: UserTraitIds.personalization }) - - const openForWork: UserTrait | undefined - = useMemo(() => memberPersonalizationTraits?.[0]?.traits?.data?.find( - (trait: UserTrait) => trait.availableForGigs !== undefined, - ), [memberPersonalizationTraits]) - function handleModifyOpenForWorkClick(): void { setIsEditMode(true) } @@ -55,7 +45,6 @@ const OpenForGigs: FC = (props: OpenForGigsProps) => { function handleModifyOpenForWorkSave(): void { setTimeout(() => { setIsEditMode(false) - mutateTraits() props.refreshProfile(props.profile.handle) triggerSurvey() }, 1000) @@ -63,8 +52,8 @@ const OpenForGigs: FC = (props: OpenForGigsProps) => { return props.canEdit || openForWork ? (
-

- {openForWork?.availableForGigs ? 'open to work' : 'not open to work'} +

+ {openForWork ? 'open to work' : 'not open to work'}

{ props.canEdit && ( @@ -78,8 +67,7 @@ const OpenForGigs: FC = (props: OpenForGigsProps) => { ) diff --git a/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx b/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx index b500c0a46..dfe8e38c9 100644 --- a/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx +++ b/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx @@ -1,15 +1,8 @@ import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react' import { toast } from 'react-toastify' -import { reject } from 'lodash' import { BaseModal, Button, InputText } from '~/libs/ui' -import { - updateOrCreateMemberTraitsAsync, - UserProfile, - UserTrait, - UserTraitCategoryNames, - UserTraitIds, -} from '~/libs/core' +import { updateMemberProfileAsync, UserProfile } from '~/libs/core' import styles from './OpenForGigsModifyModal.module.scss' @@ -17,7 +10,6 @@ interface OpenForGigsModifyModalProps { onClose: () => void onSave: () => void openForWork: boolean - memberPersonalizationTraitsFullData: UserTrait[] | undefined profile: UserProfile } @@ -35,24 +27,10 @@ const OpenForGigsModifyModal: FC = (props: OpenForG function handleOpenForWorkSave(): void { setIsSaving(true) - const updatedPersonalizationTraits: UserTrait[] - = reject( - props.memberPersonalizationTraitsFullData, - (trait: UserTrait) => trait.availableForGigs !== undefined, - ) - - updateOrCreateMemberTraitsAsync(props.profile.handle, [{ - categoryName: UserTraitCategoryNames.personalization, - traitId: UserTraitIds.personalization, - traits: { - data: [ - ...(updatedPersonalizationTraits || []), - { - availableForGigs: openForWork, - }, - ], - }, - }]) + updateMemberProfileAsync( + props.profile.handle, + { availableForGigs: openForWork }, + ) .then(() => { toast.success('Work availability updated successfully.', { position: toast.POSITION.BOTTOM_RIGHT }) props.onSave() diff --git a/src/libs/core/lib/profile/modify-user-profile.model.ts b/src/libs/core/lib/profile/modify-user-profile.model.ts index 238ecfbab..ac85292d8 100644 --- a/src/libs/core/lib/profile/modify-user-profile.model.ts +++ b/src/libs/core/lib/profile/modify-user-profile.model.ts @@ -8,6 +8,7 @@ export interface UpdateProfileRequest { streetAddr2?: string zip?: string }> + availableForGigs?: boolean, competitionCountryCode?: string homeCountryCode?: string firstName?: string diff --git a/src/libs/core/lib/profile/user-profile.model.ts b/src/libs/core/lib/profile/user-profile.model.ts index 7b22bca31..788b7e114 100644 --- a/src/libs/core/lib/profile/user-profile.model.ts +++ b/src/libs/core/lib/profile/user-profile.model.ts @@ -10,6 +10,7 @@ export interface UserProfile { streetAddr2?: string zip?: string }> + availableForGigs: boolean competitionCountryCode: string createdAt: number description: string From ec44f653c27249528498fca7bd2c2ebcbaf27733 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Nov 2023 10:34:19 +0200 Subject: [PATCH 12/37] TAL-93 - update onboarding to use member.availableForGigs --- src/apps/onboarding/src/config/index.ts | 16 +-- src/apps/onboarding/src/models/MemberInfo.ts | 1 + .../src/models/PersonalizationInfo.ts | 2 - .../src/pages/open-to-work/index.tsx | 99 +++++-------------- .../onboarding/src/redux/actions/member.ts | 20 +++- .../onboarding/src/redux/reducers/member.ts | 7 ++ 6 files changed, 56 insertions(+), 89 deletions(-) diff --git a/src/apps/onboarding/src/config/index.ts b/src/apps/onboarding/src/config/index.ts index 3719f2aa5..bbf6909a1 100644 --- a/src/apps/onboarding/src/config/index.ts +++ b/src/apps/onboarding/src/config/index.ts @@ -1,17 +1,4 @@ -export const ACTIONS: { - MEMBER: { - GET_MEMBER: string; - UPDATE_MEMBER_PHOTO_URL: string; - SET_WORKS: string; - SET_EDUCATIONS: string; - SET_PERSONALIZATIONS: string; - SET_ADDRESS: string; - SET_CONNECT_INFO: string; - SET_DESCRIPTION: string; - SET_LOADING_MEMBER_TRAITS: string; - SET_LOADING_MEMBER_INFO: string; - }; -} = { +export const ACTIONS = { MEMBER: { GET_MEMBER: 'GET_MEMBER', SET_ADDRESS: 'SET_ADDRESS', @@ -20,6 +7,7 @@ export const ACTIONS: { SET_EDUCATIONS: 'SET_EDUCATIONS', SET_LOADING_MEMBER_INFO: 'SET_LOADING_MEMBER_INFO', SET_LOADING_MEMBER_TRAITS: 'SET_LOADING_MEMBER_TRAITS', + SET_OPEN_FOR_WORK: 'SET_OPEN_FOR_WORK', SET_PERSONALIZATIONS: 'SET_PERSONALIZATIONS', SET_WORKS: 'SET_WORKS', UPDATE_MEMBER_PHOTO_URL: 'UPDATE_MEMBER_PHOTO_URL', diff --git a/src/apps/onboarding/src/models/MemberInfo.ts b/src/apps/onboarding/src/models/MemberInfo.ts index 34181f86b..23de4ea8a 100644 --- a/src/apps/onboarding/src/models/MemberInfo.ts +++ b/src/apps/onboarding/src/models/MemberInfo.ts @@ -15,6 +15,7 @@ export default interface MemberInfo { maxRating: MemberMaxRating skills: Array stats: Array + availableForGigs: boolean addresses?: MemberAddress[] country: string photoURL: string diff --git a/src/apps/onboarding/src/models/PersonalizationInfo.ts b/src/apps/onboarding/src/models/PersonalizationInfo.ts index e23a7752f..8ed82ec47 100644 --- a/src/apps/onboarding/src/models/PersonalizationInfo.ts +++ b/src/apps/onboarding/src/models/PersonalizationInfo.ts @@ -2,11 +2,9 @@ export default interface PersonalizationInfo { referAs?: string profileSelfTitle?: string shortBio?: string - availableForGigs?: boolean } export const emptyPersonalizationInfo: () => PersonalizationInfo = () => ({ - availableForGigs: true, profileSelfTitle: '', referAs: '', shortBio: '', diff --git a/src/apps/onboarding/src/pages/open-to-work/index.tsx b/src/apps/onboarding/src/pages/open-to-work/index.tsx index 7219ec831..817cebfd9 100644 --- a/src/apps/onboarding/src/pages/open-to-work/index.tsx +++ b/src/apps/onboarding/src/pages/open-to-work/index.tsx @@ -1,58 +1,32 @@ import { useNavigate } from 'react-router-dom' -import { FC, MutableRefObject, useEffect, useMemo, useRef } from 'react' +import { FC, MutableRefObject, useEffect, useRef, useState } from 'react' import { connect } from 'react-redux' +import { pick } from 'lodash' import classNames from 'classnames' import { Button, IconOutline, PageDivider } from '~/libs/ui' import { FormInputCheckbox } from '~/apps/self-service/src/components/form-elements' -import { createMemberPersonalizations, updateMemberPersonalizations } from '../../redux/actions/member' import { ProgressBar } from '../../components/progress-bar' -import { useAutoSavePersonalization, useAutoSavePersonalizationType } from '../../hooks/useAutoSavePersonalization' -import PersonalizationInfo, { emptyPersonalizationInfo } from '../../models/PersonalizationInfo' +import { updateMemberOpenForWork } from '../../redux/actions/member' import styles from './styles.module.scss' const FormInputCheckboxMiddleware: any = FormInputCheckbox as any -const blankPersonalizationInfo: PersonalizationInfo = emptyPersonalizationInfo() - -interface PageOpenToWorkContentReduxProps { - reduxPersonalizations: PersonalizationInfo[] | undefined - loadingMemberTraits: boolean -} - -interface PageOpenToWorkContentProps extends PageOpenToWorkContentReduxProps { - updateMemberPersonalizations: (infos: PersonalizationInfo[]) => void - createMemberPersonalizations: (infos: PersonalizationInfo[]) => void +interface PageOpenToWorkContentProps { + availableForGigs: boolean + updateMemberOpenForWork: (isOpenForWork: boolean) => void } export const PageOpenToWorkContent: FC = props => { const navigate: any = useNavigate() + const [loading, setLoading] = useState(false) + const shouldSavingData: MutableRefObject = useRef(false) const shouldNavigateTo: MutableRefObject = useRef('') - const { - loading, - personalizationInfo, - setPersonalizationInfo, - }: useAutoSavePersonalizationType = useAutoSavePersonalization( - props.reduxPersonalizations, - ['availableForGigs'], - props.updateMemberPersonalizations, - props.createMemberPersonalizations, - shouldSavingData, - ) - - const availableForGigsValue: boolean | undefined = useMemo(() => { - if (!personalizationInfo || personalizationInfo.availableForGigs === undefined) { - return blankPersonalizationInfo.availableForGigs - } - - return personalizationInfo.availableForGigs - }, [personalizationInfo]) - useEffect(() => { if (!loading && !shouldSavingData.current && !!shouldNavigateTo.current) { navigate(shouldNavigateTo.current) @@ -60,16 +34,18 @@ export const PageOpenToWorkContent: FC = props => { /* eslint-disable react-hooks/exhaustive-deps */ }, [loading]) - function checkToNavigateNextPage(pageUrl: string): void { - if (!personalizationInfo || personalizationInfo.availableForGigs === undefined) { - shouldNavigateTo.current = pageUrl - setPersonalizationInfo({ - ...(personalizationInfo || {}), - availableForGigs: blankPersonalizationInfo.availableForGigs, - }) - } else { - navigate(pageUrl) - } + function goToPreviousStep(): void { + navigate('../skills') + } + + function goToNextStep(): void { + navigate('../works') + } + + async function handleSaveAvailableForGigs(e: any): Promise { + setLoading(true) + await props.updateMemberOpenForWork(e.target.checked) + setLoading(false) } return ( @@ -90,15 +66,10 @@ export const PageOpenToWorkContent: FC = props => {
@@ -117,18 +88,14 @@ export const PageOpenToWorkContent: FC = props => { iconToLeft disabled={loading} icon={IconOutline.ChevronLeftIcon} - onClick={function previousPage() { - checkToNavigateNextPage('../skills') - }} + onClick={goToPreviousStep} /> @@ -137,22 +104,10 @@ export const PageOpenToWorkContent: FC = props => { ) } -const mapStateToProps: (state: any) => PageOpenToWorkContentReduxProps - = (state: any): PageOpenToWorkContentReduxProps => { - const { - loadingMemberTraits, - personalizations, - }: any = state.member - - return { - loadingMemberTraits, - reduxPersonalizations: personalizations, - } - } +const mapStateToProps: any = (state: any) => pick(state.member, 'availableForGigs') const mapDispatchToProps: any = { - createMemberPersonalizations, - updateMemberPersonalizations, + updateMemberOpenForWork, } export const PageOpenToWork: any = connect(mapStateToProps, mapDispatchToProps)(PageOpenToWorkContent) diff --git a/src/apps/onboarding/src/redux/actions/member.ts b/src/apps/onboarding/src/redux/actions/member.ts index 6f5809c26..1f3b506d5 100644 --- a/src/apps/onboarding/src/redux/actions/member.ts +++ b/src/apps/onboarding/src/redux/actions/member.ts @@ -1,6 +1,6 @@ import _ from 'lodash' -import { TokenModel, UserTraitCategoryNames, UserTraitIds } from '~/libs/core' +import { TokenModel, updateMemberProfileAsync, UserTraitCategoryNames, UserTraitIds } from '~/libs/core' import { getAsync as getAsyncToken } from '~/libs/core/lib/auth/token-functions/token.functions' import { createMemberTraits, @@ -396,6 +396,11 @@ export const setMemberPhotoUrl: any = (photoUrl: string) => ({ type: ACTIONS.MEMBER.UPDATE_MEMBER_PHOTO_URL, }) +export const setMemberOpenForWork: any = (isOpenForWork: boolean) => ({ + payload: isOpenForWork, + type: ACTIONS.MEMBER.SET_OPEN_FOR_WORK, +}) + export const updateMemberHomeAddresss: any = (addresses: MemberAddress[]) => async (dispatch: any) => { try { const tokenInfo: TokenModel = await getAsyncToken() @@ -436,3 +441,16 @@ export const updateMemberPhotoUrl: any = (photoURL: string) => async (dispatch: } catch (error) { } } + +export const updateMemberOpenForWork: any = (isOpenForWork: boolean) => async (dispatch: any) => { + try { + const tokenInfo: TokenModel = await getAsyncToken() + + await updateMemberProfileAsync( + tokenInfo.handle || '', + { availableForGigs: isOpenForWork }, + ) + dispatch(setMemberOpenForWork(isOpenForWork)) + } catch (error) { + } +} diff --git a/src/apps/onboarding/src/redux/reducers/member.ts b/src/apps/onboarding/src/redux/reducers/member.ts index 642ccf62f..f1d7442be 100644 --- a/src/apps/onboarding/src/redux/reducers/member.ts +++ b/src/apps/onboarding/src/redux/reducers/member.ts @@ -1,3 +1,4 @@ +/* eslint-disable complexity */ import _ from 'lodash' import { notifyUniNavi } from '~/apps/profiles/src/lib' @@ -19,6 +20,7 @@ const initialState: { connectInfo?: ConnectInfo loadingMemberTraits?: boolean loadingMemberInfo?: boolean + availableForGigs?: boolean } = { } @@ -35,6 +37,11 @@ const memberReducer: any = ( ...state, memberInfo: action.payload, } + case ACTIONS.MEMBER.SET_OPEN_FOR_WORK: + return { + ...state, + availableForGigs: action.payload, + } case ACTIONS.MEMBER.SET_WORKS: return { ...state, From 2bd1e6204ceb6c2d125e6bd1d1acda8bced23c18 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Nov 2023 12:11:00 +0200 Subject: [PATCH 13/37] Update react-select@5.8.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 584a7f6a9..bde1c9251 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "react-responsive-modal": "^6.2.0", "react-router-dom": "^6.4.2", "react-scripts": "5.0.1", - "react-select": "^5.5.0", + "react-select": "^5.8.0", "react-spinners": "^0.13.6", "react-stickynode": "^1.4.1", "react-toastify": "^9.0.8", diff --git a/yarn.lock b/yarn.lock index 8954e2c9d..c722b66b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16417,10 +16417,10 @@ react-scripts@5.0.1: optionalDependencies: fsevents "^2.3.2" -react-select@^5.5.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.7.0.tgz#82921b38f1fcf1471a0b62304da01f2896cd8ce6" - integrity sha512-lJGiMxCa3cqnUr2Jjtg9YHsaytiZqeNOKeibv6WF5zbK/fPegZ1hg3y/9P1RZVLhqBTs0PfqQLKuAACednYGhQ== +react-select@^5.8.0: + version "5.8.0" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.8.0.tgz#bd5c467a4df223f079dd720be9498076a3f085b5" + integrity sha512-TfjLDo58XrhP6VG5M/Mi56Us0Yt8X7xD6cDybC7yoRMUNm7BGO7qk8J0TLQOua/prb8vUOtsfnXZwfm30HGsAA== dependencies: "@babel/runtime" "^7.12.0" "@emotion/cache" "^11.4.0" From aa7d4b2d6670fa85c0ff7abffa98692a61462e4b Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Nov 2023 12:19:43 +0200 Subject: [PATCH 14/37] TSJR-217 - move skill selector menu into separate portal --- .../ModifySkillsModal.module.scss | 4 +++- .../input-multiselect/InputMultiselect.module.scss | 9 +++++---- .../input-multiselect/InputMultiselect.tsx | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/apps/profiles/src/member-profile/skills/ModifySkillsModal/ModifySkillsModal.module.scss b/src/apps/profiles/src/member-profile/skills/ModifySkillsModal/ModifySkillsModal.module.scss index 672dc7024..865df4068 100644 --- a/src/apps/profiles/src/member-profile/skills/ModifySkillsModal/ModifySkillsModal.module.scss +++ b/src/apps/profiles/src/member-profile/skills/ModifySkillsModal/ModifySkillsModal.module.scss @@ -28,7 +28,9 @@ } .skillsModalBody { - overflow: visible !important; + :global(.ms--value-container) { + max-height: 90px; + } } .principalIntroLink { diff --git a/src/libs/ui/lib/components/form/form-groups/form-input/input-multiselect/InputMultiselect.module.scss b/src/libs/ui/lib/components/form/form-groups/form-input/input-multiselect/InputMultiselect.module.scss index 61c380268..dea72e051 100644 --- a/src/libs/ui/lib/components/form/form-groups/form-input/input-multiselect/InputMultiselect.module.scss +++ b/src/libs/ui/lib/components/form/form-groups/form-input/input-multiselect/InputMultiselect.module.scss @@ -127,13 +127,14 @@ font-weight: $font-weight-medium; } } +} +.ms { + &:global(__menu-portal).ms:global(__menu-portal) { + z-index: 1001; + } &:global(__menu) { - top: 100%; - left: 0; - position: absolute; width: 100%; - z-index: 1; background-color: $tc-white; border-radius: 4px; box-shadow: 0px 4px 4px 0px rgba(0,0,0,0.25); diff --git a/src/libs/ui/lib/components/form/form-groups/form-input/input-multiselect/InputMultiselect.tsx b/src/libs/ui/lib/components/form/form-groups/form-input/input-multiselect/InputMultiselect.tsx index a70573ed8..a583ea99e 100644 --- a/src/libs/ui/lib/components/form/form-groups/form-input/input-multiselect/InputMultiselect.tsx +++ b/src/libs/ui/lib/components/form/form-groups/form-input/input-multiselect/InputMultiselect.tsx @@ -84,6 +84,18 @@ const dropdownIndicator = (dropdownIcon: ReactNode): FC => (props: any) => ( ) const InputMultiselect: FC = props => { + // we need to create a portal to append our menus so they are always visible + const menuPortalTarget = useMemo(() => { + const el = document.getElementById('input-ms-menu-target-portal') ?? document.createElement('div') + el.id = 'input-ms-menu-target-portal' + + if (!document.body.contains(el)) { + document.body.append(el) + } + + return el + }, []) + const asynSelectRef = useRef() const placeholder = useMemo(() => ( (props.value?.length as number) > 0 ? props.additionalPlaceholder ?? 'Add more...' : props.placeholder @@ -132,6 +144,7 @@ const InputMultiselect: FC = props => { props.useWrapper === false && styles.multiSelectWrap, ) } + classNames={{ valueContainer: () => 'ms--value-container' }} ref={props.inputRef ?? asynSelectRef} classNamePrefix={styles.ms} unstyled @@ -156,6 +169,7 @@ const InputMultiselect: FC = props => { openMenuOnClick={false} onKeyDown={handleKeyPress} filterOption={props.filterOption} + menuPortalTarget={menuPortalTarget} /> ) From 55704171466003adc1e6c69fea7dce2cb9412b16 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Nov 2023 12:32:12 +0200 Subject: [PATCH 15/37] TSJR-217 - update onboarding to make skills selector work --- src/apps/onboarding/src/pages/onboarding/index.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/apps/onboarding/src/pages/onboarding/index.tsx b/src/apps/onboarding/src/pages/onboarding/index.tsx index b9ca5cc86..55447227d 100644 --- a/src/apps/onboarding/src/pages/onboarding/index.tsx +++ b/src/apps/onboarding/src/pages/onboarding/index.tsx @@ -5,6 +5,7 @@ import classNames from 'classnames' import { routerContext, RouterContextData } from '~/libs/core' import { Member } from '~/apps/talent-search/src/lib/models' +import { SharedSwrConfig } from '~/libs/shared' import { EnvironmentConfig } from '~/config' import { onboardRouteId } from '../../onboarding.routes' @@ -52,11 +53,13 @@ const OnboardingContent: FC<{ } export const OnboardingWrapper: FC<{}> = () => ( -
- - - -
+ +
+ + + +
+
) export default OnboardingWrapper From a30489db137936069ce3d412a5a92fe498dd2cd0 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 16 Nov 2023 17:36:01 +0200 Subject: [PATCH 16/37] MP-356 - udpate labels, numbers --- .../SRMView/SRMView.module.scss | 0 .../tc-achievements/SRMView/SRMView.tsx | 18 +++++++ .../tc-achievements/SRMView/index.ts | 1 + .../StatsDetailsLayout/StatsDetailsLayout.tsx | 10 ++-- .../StatsSummaryBlock.module.scss | 1 + .../StatsSummaryBlock/StatsSummaryBlock.tsx | 24 +++++++++ .../SubTrackSummaryCard.module.scss | 10 +++- .../SubTrackSummaryCard.tsx | 51 ++++++++++++------- .../sub-track-view/SubTrackView.tsx | 3 +- .../tc-achievements/track-view/TrackView.tsx | 6 +-- 10 files changed, 98 insertions(+), 26 deletions(-) create mode 100644 src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.module.scss create mode 100644 src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx create mode 100644 src/apps/profiles/src/components/tc-achievements/SRMView/index.ts diff --git a/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.module.scss b/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.module.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx b/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx new file mode 100644 index 000000000..2e3835ef0 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx @@ -0,0 +1,18 @@ +import { FC } from 'react' + +import { MemberStats, SRMStats } from '~/libs/core' + +import styles from './SRMView.module.scss' + +interface SRMViewProps { + trackData: SRMStats | MemberStats +} + +const SRMView: FC = props => { + return ( +
+
+ ) +} + +export default SRMView diff --git a/src/apps/profiles/src/components/tc-achievements/SRMView/index.ts b/src/apps/profiles/src/components/tc-achievements/SRMView/index.ts new file mode 100644 index 000000000..e77f920ef --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/SRMView/index.ts @@ -0,0 +1 @@ +export { default as SRMView } from './SRMView' diff --git a/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx index 34f7a321f..961f4b736 100644 --- a/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx +++ b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx @@ -1,6 +1,8 @@ import { FC, PropsWithChildren } from 'react' import { To } from 'react-router-dom' +import { MemberStats } from '~/libs/core' + import { StatsNavHeader } from '../StatsNavHeader' import { StatsSummaryBlock } from '../StatsSummaryBlock' import { MemberStatsTrack } from '../../../hooks/useFetchActiveTracks' @@ -12,7 +14,7 @@ interface StatsDetailsLayoutProps extends PropsWithChildren { prevTitle: string backAction: To closeAction: To - trackData: MemberStatsTrack + trackData: MemberStatsTrack | MemberStats } const StatsDetailsLayout: FC = props => ( @@ -31,8 +33,10 @@ const StatsDetailsLayout: FC = props => ( trackTitle={props.title} challenges={props.trackData.challenges} wins={props.trackData.wins} - submissions={props.trackData.submissions} - ranking={props.trackData.ranking} + submissions={(props.trackData as MemberStats).submissions?.submissions ?? props.trackData.submissions} + ranking={(props.trackData as MemberStatsTrack).ranking} + rating={(props.trackData as MemberStats).rank?.rating} + volatility={(props.trackData as MemberStats).rank?.volatility} />
{props.children} diff --git a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.module.scss b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.module.scss index 46cb89cdd..5feefe8e4 100644 --- a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.module.scss +++ b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.module.scss @@ -32,5 +32,6 @@ &Label { line-height: 23px; + text-transform: capitalize; } } diff --git a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx index 3da716b8e..0b4ab3917 100644 --- a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx +++ b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx @@ -1,5 +1,7 @@ import { FC } from 'react' +import { ratingToCSScolor } from '~/libs/core' + import styles from './StatsSummaryBlock.module.scss' interface StatsSummaryBlockProps { @@ -8,6 +10,8 @@ interface StatsSummaryBlockProps { wins?: number submissions?: number ranking?: number + rating?: number + volatility?: number } const StatsSummaryBlock: FC = props => ( @@ -64,6 +68,26 @@ const StatsSummaryBlock: FC = props => (
)} + {props.volatility !== undefined && ( +
+ + {props.volatility} + + + volatility + +
+ )} + {props.rating !== undefined && ( +
+ + {props.rating} + + + rating + +
+ )} ) diff --git a/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.module.scss b/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.module.scss index 36ac70714..ee65f7ade 100644 --- a/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.module.scss +++ b/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.module.scss @@ -13,7 +13,14 @@ .stats { display: flex; align-items: center; - gap: $sp-4; + gap: $sp-4 $sp-2; + flex-wrap: wrap; + + &Wrap { + display: flex; + align-items: center; + gap: $sp-4; + } } .statsItem { @@ -31,6 +38,7 @@ &Label { color: $black-80; + text-transform: capitalize; } &Icon { diff --git a/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.tsx b/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.tsx index 26629a468..13f8df48c 100644 --- a/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.tsx +++ b/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.tsx @@ -8,9 +8,10 @@ import { subTrackLabelToHumanName, WinnerIcon } from '../../../lib' import styles from './SubTrackSummaryCard.module.scss' interface SubTrackSummaryCardProps { - submissions: number title: string wins: number + challenges?: number + submissions?: number } const SubTrackSummaryCard: FC = props => ( @@ -20,23 +21,37 @@ const SubTrackSummaryCard: FC = props => ( {subTrackLabelToHumanName(props.title)} -
-
- - - {props.wins} - - - wins - -
-
- - {props.submissions} - - - submissions - +
+
+
+ + + {props.wins} + + + wins + +
+ {props.submissions !== undefined && ( +
+ + {props.submissions} + + + submissions + +
+ )} + {props.challenges !== undefined && props.submissions === undefined && ( +
+ + {props.challenges} + + + challenges + +
+ )}
diff --git a/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx b/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx index 73d718ee4..4b13d802b 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx +++ b/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx @@ -7,6 +7,7 @@ import { useFetchSubTrackData } from '../../../hooks' import { StatsDetailsLayout } from '../../../components/tc-achievements/StatsDetailsLayout' import { getUserProfileRoute, getUserProfileStatsRoute } from '../../../profiles.routes' import { subTrackLabelToHumanName } from '../../../lib' +import { SRMView } from '../../../components/tc-achievements/SRMView' import styles from './SubTrackView.module.scss' @@ -29,7 +30,7 @@ const SubTrackView: FC = props => { trackData={subTrackData} > {subTrackData.name === 'MARATHON_MATCH' || subTrackData.name === 'SRM' ? ( - 'SRM TEst' + ) : subTrackData.name === 'WEB_DESIGNS' ? ( 'WEB_DESIGNS test' ) : ( diff --git a/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx b/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx index 671f32235..f97bc444f 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx +++ b/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx @@ -17,7 +17,6 @@ interface TrackViewProps { const TrackView: FC = props => { const params = useParams() const trackData = useFetchTrackData(props.profile.handle, params.trackType) - console.log('here', trackData) return (
@@ -35,10 +34,11 @@ const TrackView: FC = props => { key={subTrack.name} > ))} From c9d137baf0e984a3a03dbdbe087fe8c7d744eedb Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 20 Nov 2023 16:34:49 +0200 Subject: [PATCH 17/37] MP-356 - Update member profile: add rating distribution graph using D3js --- .../AssemblyDetailsModal.tsx | 4 +- .../ContentCreationDetailsModal.tsx | 4 +- .../GenericSubtrackDetailsModal.tsx | 4 +- .../MMDetailsModal/MMDetailsModal.tsx | 4 +- .../SRMDetailsModal/SRMDetailsModal.tsx | 4 +- .../TestScenariosDetailsModal.tsx | 4 +- .../UIPrototypeDetailsModal.tsx | 4 +- .../ChallengeHistoryCard.module.scss | 63 +++++++++++++++ .../ChallengeHistoryCard.tsx | 44 +++++++++++ .../ChallengeHistoryCard/index.ts | 1 + .../ChallengeHistoryView.module.scss | 13 +++ .../ChallengeHistoryView.tsx | 33 ++++++++ .../ChallengeHistoryView/index.ts | 1 + .../ChallengeWinsBanner.tsx | 5 -- .../MemberStatsBlock/MemberStatsBlock.tsx | 2 +- .../SRMView/SRMView.module.scss | 18 +++++ .../tc-achievements/SRMView/SRMView.tsx | 79 ++++++++++++++++++- .../StatsSummaryBlock.module.scss | 3 +- .../StatsSummaryBlock/StatsSummaryBlock.tsx | 7 +- .../SubTrackSummaryCard.module.scss | 1 + .../src/hooks/useFetchActiveTracks.tsx | 8 +- .../src/hooks/useRatingDistroOptions.tsx | 76 +++++++++++++++--- .../sub-track-view/SubTrackView.tsx | 8 +- .../profile-functions/rating.functions.ts | 10 +-- src/libs/core/lib/profile/user-stats.model.ts | 10 ++- 25 files changed, 357 insertions(+), 53 deletions(-) create mode 100644 src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.module.scss create mode 100644 src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx create mode 100644 src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/index.ts create mode 100644 src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.module.scss create mode 100644 src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx create mode 100644 src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/index.ts diff --git a/src/apps/profiles/src/components/AssemblyDetailsModal/AssemblyDetailsModal.tsx b/src/apps/profiles/src/components/AssemblyDetailsModal/AssemblyDetailsModal.tsx index 25a8577ae..c0e0cde62 100644 --- a/src/apps/profiles/src/components/AssemblyDetailsModal/AssemblyDetailsModal.tsx +++ b/src/apps/profiles/src/components/AssemblyDetailsModal/AssemblyDetailsModal.tsx @@ -4,8 +4,8 @@ import HighchartsReact from 'highcharts-react-official' import { BaseModal, LoadingSpinner } from '~/libs/ui' import { + getRatingColor, MemberStats, - ratingToCSScolor, UserProfile, UserStatsDistributionResponse, UserStatsHistory, @@ -66,7 +66,7 @@ const AssemblyDetailsModal: FC = (props: AssemblyDeta
{props.assemblyStats?.rank.rating} diff --git a/src/apps/profiles/src/components/ContentCreationDetailsModal/ContentCreationDetailsModal.tsx b/src/apps/profiles/src/components/ContentCreationDetailsModal/ContentCreationDetailsModal.tsx index 793701e25..56df1f9ec 100644 --- a/src/apps/profiles/src/components/ContentCreationDetailsModal/ContentCreationDetailsModal.tsx +++ b/src/apps/profiles/src/components/ContentCreationDetailsModal/ContentCreationDetailsModal.tsx @@ -5,8 +5,8 @@ import HighchartsReact from 'highcharts-react-official' import { BaseModal, LoadingSpinner } from '~/libs/ui' import { + getRatingColor, MemberStats, - ratingToCSScolor, UserProfile, UserStatsDistributionResponse, UserStatsHistory, @@ -67,7 +67,7 @@ const ContentCreationDetailsModal: FC = (props
{props.contentCreationStats?.rank.rating} diff --git a/src/apps/profiles/src/components/GenericSubtrackDetailsModal/GenericSubtrackDetailsModal.tsx b/src/apps/profiles/src/components/GenericSubtrackDetailsModal/GenericSubtrackDetailsModal.tsx index 81a72249e..327f16228 100644 --- a/src/apps/profiles/src/components/GenericSubtrackDetailsModal/GenericSubtrackDetailsModal.tsx +++ b/src/apps/profiles/src/components/GenericSubtrackDetailsModal/GenericSubtrackDetailsModal.tsx @@ -6,8 +6,8 @@ import HighchartsReact from 'highcharts-react-official' import { BaseModal } from '~/libs/ui' import { + getRatingColor, MemberStats, - ratingToCSScolor, UserProfile, UserStatsDistributionResponse, UserStatsHistory, @@ -68,7 +68,7 @@ const GenericSubtrackDetailsModal: FC = (props
{props.genericStats?.rank?.rating || 0} diff --git a/src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.tsx b/src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.tsx index bf8e83329..a0fbd374d 100644 --- a/src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.tsx +++ b/src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.tsx @@ -5,8 +5,8 @@ import HighchartsReact from 'highcharts-react-official' import { BaseModal, LoadingSpinner } from '~/libs/ui' import { + getRatingColor, MemberStats, - ratingToCSScolor, UserProfile, UserStatsDistributionResponse, UserStatsHistory, @@ -68,7 +68,7 @@ const MMDetailsModal: FC = (props: MMDetailsModalProps) =>
{props.MMStats?.rank.rating} diff --git a/src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.tsx b/src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.tsx index 32e8457a9..b4f2b94b4 100644 --- a/src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.tsx +++ b/src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.tsx @@ -7,7 +7,7 @@ import HighchartsReact from 'highcharts-react-official' import { BaseModal, Button, LoadingSpinner } from '~/libs/ui' import { - ratingToCSScolor, + getRatingColor, SRMStats, UserProfile, UserStatsDistributionResponse, @@ -72,7 +72,7 @@ const SRMDetailsModal: FC = (props: SRMDetailsModalProps)
{props.SRMStats?.rank.rating} diff --git a/src/apps/profiles/src/components/TestScenariosDetailsModal/TestScenariosDetailsModal.tsx b/src/apps/profiles/src/components/TestScenariosDetailsModal/TestScenariosDetailsModal.tsx index 39e476c6b..83e1219d9 100644 --- a/src/apps/profiles/src/components/TestScenariosDetailsModal/TestScenariosDetailsModal.tsx +++ b/src/apps/profiles/src/components/TestScenariosDetailsModal/TestScenariosDetailsModal.tsx @@ -5,8 +5,8 @@ import HighchartsReact from 'highcharts-react-official' import { BaseModal, LoadingSpinner } from '~/libs/ui' import { + getRatingColor, MemberStats, - ratingToCSScolor, UserProfile, UserStatsDistributionResponse, UserStatsHistory, @@ -67,7 +67,7 @@ const TestScenariosDetailsModal: FC = (props: Te
{props.testScenStats?.rank.rating} diff --git a/src/apps/profiles/src/components/UIPrototypeDetailsModal/UIPrototypeDetailsModal.tsx b/src/apps/profiles/src/components/UIPrototypeDetailsModal/UIPrototypeDetailsModal.tsx index 2b80de115..db20391f2 100644 --- a/src/apps/profiles/src/components/UIPrototypeDetailsModal/UIPrototypeDetailsModal.tsx +++ b/src/apps/profiles/src/components/UIPrototypeDetailsModal/UIPrototypeDetailsModal.tsx @@ -5,8 +5,8 @@ import HighchartsReact from 'highcharts-react-official' import { BaseModal, LoadingSpinner } from '~/libs/ui' import { + getRatingColor, MemberStats, - ratingToCSScolor, UserProfile, UserStatsDistributionResponse, UserStatsHistory, @@ -67,7 +67,7 @@ const UIPrototypeDetailsModal: FC = (props: UIProt
{props.uiPrototypeStats?.rank.rating} diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.module.scss b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.module.scss new file mode 100644 index 000000000..db2821406 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.module.scss @@ -0,0 +1,63 @@ +@import '@libs/ui/styles/includes'; + +.wrap { + border: 1px solid $black-20; + border-radius: $sp-2; + padding: $sp-2; + padding-left: $sp-4; + + gap: $sp-3; + display: grid; + grid-template-columns: 1fr 20px; +} + +.contentWrap { + display: block; + max-width: 100%; + overflow: hidden; +} + +.icon { + display: flex; + align-items: center; + width: 20px; +} + +.title { + margin-bottom: $sp-2; + text-wrap: nowrap; + text-overflow: ellipsis; + overflow: hidden; + width: 100%; +} + + +.statsWrap { + display: flex; + align-items: center; + gap: $sp-4 $sp-2; + flex-wrap: wrap; +} + +.statsItem { + display: flex; + align-items: center; + gap: $sp-1; + + &Value { + color: $turq-160; + font-family: $font-barlow-condensed; + font-weight: $font-weight-medium; + font-size: 26px; + line-height: 34px; + } + + &Label { + color: $black-80; + text-transform: capitalize; + } + + &Icon { + margin-left: auto; + } +} diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx new file mode 100644 index 000000000..c0b3649e5 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx @@ -0,0 +1,44 @@ +import { FC } from 'react' + +import { IconSolid } from '~/libs/ui' +import { getRatingColor, StatsHistory } from '~/libs/core' + +import styles from './ChallengeHistoryCard.module.scss' + +interface ChallengeHistoryCardProps { + challenge: StatsHistory +} + +const ChallengeHistoryCard: FC = props => ( +
+
+
+ + {props.challenge.challengeName} + +
+
+ {props.challenge.newRating !== undefined && ( +
+ + {props.challenge.newRating} + + + + rating + + +
+ )} +
+
+
+ +
+
+) + +export default ChallengeHistoryCard diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/index.ts b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/index.ts new file mode 100644 index 000000000..932b27bd4 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/index.ts @@ -0,0 +1 @@ +export { default as ChallengeHistoryCard } from './ChallengeHistoryCard' diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.module.scss b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.module.scss new file mode 100644 index 000000000..e560d7e79 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.module.scss @@ -0,0 +1,13 @@ +@import '@libs/ui/styles/includes'; + +.wrap { + display: flex; + flex-wrap: wrap; + gap: $sp-6; + max-height: 85vh; + overflow: auto; + + > * { + width: calc(33% - 14px); + } +} diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx new file mode 100644 index 000000000..e6255960b --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx @@ -0,0 +1,33 @@ +import { FC } from 'react' +import { find, get } from 'lodash' + +import { MemberStats, StatsHistory, UserProfile, UserStatsHistory, useStatsHistory } from '~/libs/core' + +import { ChallengeHistoryCard } from './ChallengeHistoryCard' +import styles from './ChallengeHistoryView.module.scss' + +interface ChallengeHistoryViewProps { + profile: UserProfile + trackData: MemberStats +} + +const ChallengeHistoryView: FC = props => { + const statsHistory: UserStatsHistory | undefined = useStatsHistory(props.profile?.handle) + const trackHistory: StatsHistory[] = get( + find(get(statsHistory, `${props.trackData.parent}`, []), { name: props.trackData.name }), + 'history', + [], + ) + return ( +
+ {trackHistory.map(challenge => ( + + ))} +
+ ) +} + +export default ChallengeHistoryView diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/index.ts b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/index.ts new file mode 100644 index 000000000..a98323c20 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/index.ts @@ -0,0 +1 @@ +export { default as ChallengeHistoryView } from './ChallengeHistoryView' diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx b/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx index 22d24642d..fd1136398 100644 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx @@ -36,11 +36,6 @@ const ChallengeWinsBanner: FC = (props: ChallengeWinsB () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'CONTENT_CREATION'), [props.memberStats], ) - const specStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'SPECIFICATION'), - [props.memberStats], - ) const copilotPostingStats: MemberStats | undefined = useMemo( () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'COPILOT_POSTING'), diff --git a/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx index 5ff513519..8970f461a 100644 --- a/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx +++ b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx @@ -7,9 +7,9 @@ import { IconOutline } from '~/libs/ui' import { useFetchActiveTracks } from '../../../hooks' import { getUserProfileStatsRoute } from '../../../profiles.routes' +import { WinnerIcon } from '../../../lib' import styles from './MemberStatsBlock.module.scss' -import { WinnerIcon } from '../../../lib' interface MemberStatsBlockProps { profile: UserProfile diff --git a/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.module.scss b/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.module.scss index e69de29bb..e5d8df91c 100644 --- a/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.module.scss +++ b/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.module.scss @@ -0,0 +1,18 @@ +@import '@libs/ui/styles/includes'; + +.chartTitle { + font-size: 18px; + line-height: 24px; + font-family: $font-roboto; + text-align: center; + text-transform: uppercase; + margin: $sp-9 0 $sp-6; +} + +.btnsGroup { + display: flex; + margin-bottom: $sp-4; + .btn { + border-radius: 0; + } +} diff --git a/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx b/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx index 2e3835ef0..a0af15b2e 100644 --- a/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx +++ b/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx @@ -1,16 +1,91 @@ -import { FC } from 'react' +import { FC, useState } from 'react' +import { get } from 'lodash' +import AnnotationsModule from 'highcharts/modules/annotations' +import Highcharts from 'highcharts' +import HighchartsReact from 'highcharts-react-official' -import { MemberStats, SRMStats } from '~/libs/core' +import { + MemberStats, + SRMStats, + UserProfile, + UserStatsDistributionResponse, + UserStatsHistory, + useStatsDistribution, + useStatsHistory, +} from '~/libs/core' +import { Button } from '~/libs/ui' + +import { useRatingDistroOptions, useRatingHistoryOptions } from '../../../hooks' import styles from './SRMView.module.scss' interface SRMViewProps { + profile: UserProfile trackData: SRMStats | MemberStats } +AnnotationsModule(Highcharts) + +enum Graphs { + distribution = 'distribution', + history = 'history', +} + const SRMView: FC = props => { + const [activeGraph, setActiveGraph] = useState(Graphs.distribution) + const statsHistory: UserStatsHistory | undefined = useStatsHistory(props.profile?.handle) + + const trackName: string = (props.trackData as MemberStats).name ?? 'SRM' + const ratingHistoryOptions: Highcharts.Options | undefined + = useRatingHistoryOptions( + get(statsHistory, `DATA_SCIENCE.${trackName}.history`), + `${trackName} Rating`, + 'date', + 'rating', + ) + + const memberStatsDist: UserStatsDistributionResponse | undefined = useStatsDistribution({ + filter: `track=DATA_SCIENCE&subTrack=${trackName}`, + }) + + const ratingDistributionOptions: Highcharts.Options | undefined + = useRatingDistroOptions(memberStatsDist?.distribution || {}, props.trackData.rank.rating) + return (
+
+
+ {activeGraph === Graphs.history && ratingHistoryOptions && ( + + )} + + {activeGraph === Graphs.distribution && ratingDistributionOptions && ( + + )}
) } diff --git a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.module.scss b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.module.scss index 5feefe8e4..c07882abf 100644 --- a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.module.scss +++ b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.module.scss @@ -14,7 +14,8 @@ .summary { display: flex; align-items: center; - gap: $sp-12; + flex-wrap: wrap; + gap: $sp-4 $sp-12; } .summaryItem { diff --git a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx index 0b4ab3917..4cc3ffb34 100644 --- a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx +++ b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { ratingToCSScolor } from '~/libs/core' +import { getRatingColor } from '~/libs/core' import styles from './StatsSummaryBlock.module.scss' @@ -80,7 +80,10 @@ const StatsSummaryBlock: FC = props => ( )} {props.rating !== undefined && (
- + {props.rating} diff --git a/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.module.scss b/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.module.scss index ee65f7ade..b41d345c1 100644 --- a/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.module.scss +++ b/src/apps/profiles/src/components/tc-achievements/SubTrackSummaryCard/SubTrackSummaryCard.module.scss @@ -43,5 +43,6 @@ &Icon { margin-left: auto; + margin-right: -9px; } } diff --git a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx index 7a02430c1..48c7db343 100644 --- a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx +++ b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx @@ -37,24 +37,27 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => MARATHON_MATCH: (memberStats?.DATA_SCIENCE?.MARATHON_MATCH && ({ ...memberStats.DATA_SCIENCE.MARATHON_MATCH, name: 'MARATHON_MATCH', + parent: 'DATA_SCIENCE', + })) as MemberStats, SRM: (memberStats?.DATA_SCIENCE?.SRM && ({ ...memberStats.DATA_SCIENCE.SRM, name: 'SRM', + parent: 'DATA_SCIENCE', })) as SRMStats & {name: string}, }), [memberStats]) // Create mappings for the subtracks, by the subtrack name, so we can easily access it later on const designSubTracks: {[key: string]: MemberStats} = useMemo(() => ( memberStats?.DESIGN?.subTracks.reduce((all, subTrack) => { - all[subTrack.name] = subTrack + all[subTrack.name] = { ...subTrack, parent: 'DESIGN.subTracks' } return all }, {} as {[key: string]: MemberStats}) ?? {} ), [memberStats]) const developSubTracks: {[key: string]: MemberStats} = useMemo(() => ( memberStats?.DEVELOP?.subTracks.reduce((all, subTrack) => { - all[subTrack.name] = subTrack + all[subTrack.name] = { ...subTrack, parent: 'DEVELOP.subTracks' } return all }, {} as {[key: string]: MemberStats}) ?? {} ), [memberStats]) @@ -85,6 +88,7 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => developSubTracks.CODE, developSubTracks.ASSEMBLY_COMPETITION, developSubTracks.UI_PROTOTYPE_COMPETITION, + developSubTracks.SPECIFICATION, ].filter(Boolean)) ), [developSubTracks]) diff --git a/src/apps/profiles/src/hooks/useRatingDistroOptions.tsx b/src/apps/profiles/src/hooks/useRatingDistroOptions.tsx index 48e9581c3..08e9c1ea1 100644 --- a/src/apps/profiles/src/hooks/useRatingDistroOptions.tsx +++ b/src/apps/profiles/src/hooks/useRatingDistroOptions.tsx @@ -1,12 +1,59 @@ import { useMemo } from 'react' -import { isEmpty, keys } from 'lodash' +import { isEmpty, toPairs } from 'lodash' import AnnotationsModule from 'highcharts/modules/annotations' import Highcharts from 'highcharts' -import { UserStatsDistributionResponse } from '~/libs/core' +import { getRatingColor, UserStatsDistributionResponse } from '~/libs/core' AnnotationsModule(Highcharts) +interface TrackRange { + end: number + name: string + value: number + start: number +} + +/** + * Parses the api distribution data and returns more easy to use objects (TrackRange) + * @param distribution - The api data + * @returns - The optimized objects + */ +const getRanges = (distribution: any): TrackRange[] => ( + toPairs(distribution as {[key: string]: number}) + .map(([name, number]) => { + const match = (name.match(/ratingRange(\d+)To(\d+)/) ?? []).slice(1) + const [start, end] = match.map((rating: string) => parseInt(rating, 10)) + + return { + end, + name, + start, + value: number, + } + }) + .sort((a, b) => a.start - b.start) +) + +/** + * Trims the start & end 0 values from the ranges array + * @param ranges - The ranges to be parsed and trimmed + * @returns - The trimmed ranges + */ +const getNonZeroRanges = (ranges: TrackRange[]): TrackRange[] => { + let st = 0 + while (ranges[st]?.value === 0) { + st += 1 + } + + let end = ranges.length - 1 + while (end > st && ranges[end]?.value === 0) { + end -= 1 + } + + return ranges.slice(st, end + 1) +} + export const RATING_DISTRO_CHART_CONFIG: Highcharts.Options = { chart: { type: 'column', @@ -17,6 +64,16 @@ export const RATING_DISTRO_CHART_CONFIG: Highcharts.Options = { legend: { enabled: false, }, + plotOptions: { + column: { + dataLabels: { + enabled: false, + }, + groupPadding: 0.025, + minPointLength: 5, + pointPadding: 0, + }, + }, title: { text: 'RATING DISTRIBUTION', }, @@ -39,18 +96,16 @@ export function useRatingDistroOptions( ): Highcharts.Options | undefined { const ratingDistributionOptions: Highcharts.Options | undefined = useMemo(() => { const options: Highcharts.Options = RATING_DISTRO_CHART_CONFIG - const distroGroups = keys(ratingDistro) + const ranges = getNonZeroRanges(getRanges(ratingDistro)) if (isEmpty(ratingDistro)) return undefined if (!!memberRating) { // if member is defined, find the group that the member belongs to // and add an annotation to the chart - const memberRatingGroup: string = distroGroups.find((key: string) => { - const [min, max] = key.split('ratingRange')[1].split('To') - .map((n: string) => parseInt(n, 10)) - return memberRating >= min && memberRating <= max - }) || '' + const memberRatingGroup: string = ranges.find((d: TrackRange) => ( + memberRating >= d.start && memberRating <= d.end + ))?.name || '' options.annotations = [{ labels: [{ @@ -61,8 +116,9 @@ export function useRatingDistroOptions( } options.series = [{ - data: distroGroups - .map((key: string) => [ratingDistro[key], key, key.split('ratingRange')[1]]), + colorByPoint: true, + colors: ranges.map(r => getRatingColor(r.start)), + data: ranges.map(r => [r.value, r.name, `Rating Range: ${r.start}-${r.end}`]), keys: ['y', 'id', 'name'], type: 'column', }] diff --git a/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx b/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx index 4b13d802b..d43d238dd 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx +++ b/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx @@ -5,6 +5,7 @@ import { UserProfile } from '~/libs/core' import { useFetchSubTrackData } from '../../../hooks' import { StatsDetailsLayout } from '../../../components/tc-achievements/StatsDetailsLayout' +import { ChallengeHistoryView } from '../../../components/tc-achievements/ChallengeHistoryView' import { getUserProfileRoute, getUserProfileStatsRoute } from '../../../profiles.routes' import { subTrackLabelToHumanName } from '../../../lib' import { SRMView } from '../../../components/tc-achievements/SRMView' @@ -20,7 +21,7 @@ const SubTrackView: FC = props => { const { trackData, ...subTrackData }: any = useFetchSubTrackData(props.profile.handle, params.trackType, params.subTrack) - return ( + return trackData && subTrackData && (
= props => { trackData={subTrackData} > {subTrackData.name === 'MARATHON_MATCH' || subTrackData.name === 'SRM' ? ( - + ) : subTrackData.name === 'WEB_DESIGNS' ? ( 'WEB_DESIGNS test' ) : ( - 'Other test' + )} - test
) diff --git a/src/libs/core/lib/profile/profile-functions/rating.functions.ts b/src/libs/core/lib/profile/profile-functions/rating.functions.ts index 05d1c6620..f9d3c6f83 100644 --- a/src/libs/core/lib/profile/profile-functions/rating.functions.ts +++ b/src/libs/core/lib/profile/profile-functions/rating.functions.ts @@ -1,5 +1,3 @@ -import { CSSProperties } from 'react' - export const TC_RATING_COLORS: Array<{ color: string, limit: number }> = [{ color: '#555555' /* Grey */, limit: 900, @@ -20,13 +18,9 @@ export const TC_RATING_COLORS: Array<{ color: string, limit: number }> = [{ /** * Inline CSS for rating color */ -export function ratingToCSScolor(rating: number): CSSProperties { +export function getRatingColor(rating: number): string { let i: number = 0 while (TC_RATING_COLORS[i].limit <= rating) i += 1 - const color: string = TC_RATING_COLORS[i].color || '#2a2a2a' - - return { - color, - } + return TC_RATING_COLORS[i].color || '#2a2a2a' } diff --git a/src/libs/core/lib/profile/user-stats.model.ts b/src/libs/core/lib/profile/user-stats.model.ts index 754d8964a..a99d9ad04 100644 --- a/src/libs/core/lib/profile/user-stats.model.ts +++ b/src/libs/core/lib/profile/user-stats.model.ts @@ -28,6 +28,7 @@ export type SRMStats = { volatility: number } wins: number + parent?: string } export type MemberStats = { @@ -49,6 +50,7 @@ export type MemberStats = { submissions: { submissions: number } + parent?: string } export type UserStats = { @@ -101,10 +103,10 @@ export type UserStats = { export type StatsHistory = { challengeId: number challengeName: string - date: number - percentile: number - placement: number - rating: number + date?: number + percentile?: number + placement?: number + rating?: number newRating: number ratingDate: number } From 00acdf82b8c5afe8e61774fd9498caa794f924f5 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 21 Nov 2023 10:04:27 +0200 Subject: [PATCH 18/37] MP-356 - statistics views for all subtracks --- .../MMDetailsModal/MMDetailsModal.tsx | 2 - .../SRMDetailsModal/SRMDetailsModal.tsx | 2 - .../ChallengeHistoryView.module.scss | 2 + .../ChallengeHistoryView.tsx | 2 +- .../ChallengeWin/ChallengeWin.module.scss | 18 - .../ChallengeWin/ChallengeWin.tsx | 23 - .../ChallengeWinsBanner/ChallengeWin/index.ts | 1 - .../ChallengeWinsBanner.module.scss | 41 -- .../ChallengeWinsBanner.tsx | 425 ------------------ .../ChallengeWinsBanner/index.ts | 1 - .../DetailedTrackView.module.scss | 25 ++ .../DetailedTrackView/DetailedTrackView.tsx | 124 +++++ .../DetailedTrackView/index.ts | 1 + .../DevelopTrackView.module.scss | 0 .../DevelopTrackView/DevelopTrackView.tsx | 49 ++ .../tc-achievements/DevelopTrackView/index.ts | 1 + .../SRMView/SRMView.module.scss | 18 - .../tc-achievements/SRMView/SRMView.tsx | 93 ++-- .../StatsDetailsLayout/StatsDetailsLayout.tsx | 2 + .../StatsSummaryBlock/StatsSummaryBlock.tsx | 28 +- .../src/hooks/useFetchActiveTracks.tsx | 28 +- .../src/hooks/useRatingDistroOptions.tsx | 4 +- .../src/hooks/useRatingHistoryOptions.tsx | 19 +- .../sub-track-view/SubTrackView.tsx | 7 +- src/libs/core/lib/profile/user-stats.model.ts | 6 +- 25 files changed, 299 insertions(+), 623 deletions(-) delete mode 100644 src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.module.scss delete mode 100644 src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.tsx delete mode 100644 src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/index.ts delete mode 100644 src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.module.scss delete mode 100644 src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx delete mode 100644 src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/index.ts create mode 100644 src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.module.scss create mode 100644 src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.tsx create mode 100644 src/apps/profiles/src/components/tc-achievements/DetailedTrackView/index.ts create mode 100644 src/apps/profiles/src/components/tc-achievements/DevelopTrackView/DevelopTrackView.module.scss create mode 100644 src/apps/profiles/src/components/tc-achievements/DevelopTrackView/DevelopTrackView.tsx create mode 100644 src/apps/profiles/src/components/tc-achievements/DevelopTrackView/index.ts diff --git a/src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.tsx b/src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.tsx index a0fbd374d..6aefb4c3a 100644 --- a/src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.tsx +++ b/src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.tsx @@ -36,8 +36,6 @@ const MMDetailsModal: FC = (props: MMDetailsModalProps) => = useRatingHistoryOptions( statsHistory?.DATA_SCIENCE?.MARATHON_MATCH?.history, 'Marathon Match Rating', - 'date', - 'rating', ) const memberStatsDist: UserStatsDistributionResponse | undefined = useStatsDistribution({ diff --git a/src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.tsx b/src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.tsx index b4f2b94b4..546039b9c 100644 --- a/src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.tsx +++ b/src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.tsx @@ -42,8 +42,6 @@ const SRMDetailsModal: FC = (props: SRMDetailsModalProps) = useRatingHistoryOptions( statsHistory?.DATA_SCIENCE?.SRM?.history, 'SRM Rating', - 'date', - 'rating', ) const memberStatsDist: UserStatsDistributionResponse | undefined = useStatsDistribution({ diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.module.scss b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.module.scss index e560d7e79..df9dfc7ac 100644 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.module.scss +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.module.scss @@ -6,6 +6,8 @@ gap: $sp-6; max-height: 85vh; overflow: auto; + padding-right: $sp-4; + margin-right: -$sp-4; > * { width: calc(33% - 14px); diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx index e6255960b..7bdb73479 100644 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx @@ -14,7 +14,7 @@ interface ChallengeHistoryViewProps { const ChallengeHistoryView: FC = props => { const statsHistory: UserStatsHistory | undefined = useStatsHistory(props.profile?.handle) const trackHistory: StatsHistory[] = get( - find(get(statsHistory, `${props.trackData.parent}`, []), { name: props.trackData.name }), + find(get(statsHistory, `${props.trackData.path}`, []), { name: props.trackData.name }), 'history', [], ) diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.module.scss b/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.module.scss deleted file mode 100644 index 1dd88d651..000000000 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.winWrapper { - display: flex; - flex-direction: column; - align-items: center; - padding: 0 $sp-2; - cursor: pointer; - min-width: 115px; - - .winCnt { - font-family: $font-barlow-condensed; - font-size: 32px; - font-weight: 500; - line-height: 34px; - margin-top: $sp-4; - } -} diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.tsx b/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.tsx deleted file mode 100644 index 05e0db56c..000000000 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/ChallengeWin.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { FC } from 'react' - -import styles from './ChallengeWin.module.scss' - -interface ChallengeWinProps { - typeName: string - onClick: () => void - winCnt: number - winLabel?: string -} - -const ChallengeWin: FC = (props: ChallengeWinProps) => ( -
-

- {props.winCnt} - {' '} -

-

{props.winLabel || 'WINS'}

-

{props.typeName}

-
-) - -export default ChallengeWin diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/index.ts b/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/index.ts deleted file mode 100644 index 575b4cc38..000000000 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWin/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as ChallengeWin } from './ChallengeWin' diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.module.scss b/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.module.scss deleted file mode 100644 index 72ada946e..000000000 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.module.scss +++ /dev/null @@ -1,41 +0,0 @@ -@import "@libs/ui/styles/includes"; - - -.containerWrap { - flex: 1; - - >p { - font-size: 20px; - } - - .container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - border-radius: 15px; - background-image: linear-gradient(90deg, #7B21A7, #1974AD); - color: $tc-white; - padding: $sp-8; - min-height: 100%; - - @include ltelg { - padding: $sp-4; - } - - :global(.body-large-bold) { - text-align: center; - } - - .innerWrapper { - - .wins { - display: flex; - flex-wrap: wrap; - justify-content: center; - width: 100%; - margin-bottom: $sp-4; - } - } - } -} \ No newline at end of file diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx b/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx deleted file mode 100644 index fd1136398..000000000 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/ChallengeWinsBanner.tsx +++ /dev/null @@ -1,425 +0,0 @@ -/* eslint-disable */ -import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react' -import { bind } from 'lodash' - -import { MemberStats, UserProfile, UserStats } from '~/libs/core' - -import { subTrackLabelToHumanName } from '../../../lib/helpers' -import { - AssemblyDetailsModal, - BugHuntDetailsModal, - CodeDetailsModal, - ContentCreationDetailsModal, - CopilotDetailsModal, - DesignF2FDetailsModal, - F2FDetailsModal, - GenericSubtrackDetailsModal, - LogoDesignDetailsModal, - MMDetailsModal, - SRMDetailsModal, - TestScenariosDetailsModal, - UIPrototypeDetailsModal, - WebDesignDetailsModal, -} from '../../../components' - -import { ChallengeWin } from './ChallengeWin' -import styles from './ChallengeWinsBanner.module.scss' - -interface ChallengeWinsBannerProps { - memberStats: UserStats - profile: UserProfile -} - -const ChallengeWinsBanner: FC = (props: ChallengeWinsBannerProps) => { - const contentCreationStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'CONTENT_CREATION'), - [props.memberStats], - ) - const copilotPostingStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'COPILOT_POSTING'), - [props.memberStats], - ) - const conceptStats: MemberStats | undefined - = useMemo( - () => props.memberStats?.DEVELOP?.subTracks.find(subTrack => subTrack.name === 'CONCEPTUALIZATION'), - [props.memberStats], - ) - - const [modalVisibilityMap, setModalVisibilityMap]: [ - { [key: string]: boolean }, - Dispatch> - ] = useState<{ [key: string]: boolean }>({ - APPLICATION_FRONT_END_DESIGN: false, - ARCHITECTURE: false, - ASSEMBLY_COMPETITION: false, - BANNERS_OR_ICONS: false, - BUG_HUNT: false, - CODE: false, - CONCEPTUALIZATION: false, - CONTENT_CREATION: false, - COPILOT: false, - COPILOT_POSTING: false, - DESIGN: false, - DESIGN_FIRST_2_FINISH: false, - DEVELOPMENT: false, - DS: false, - FIRST_2_FINISH: false, - FRONT_END_FLASH: false, - LOGO_DESIGN: false, - PRINT_OR_PRESENTATION: false, - SPECIFICATION: false, - SRM: false, - STUDIO_OTHER: false, - TEST_SCENARIOS: false, - TEST_SUITES: false, - UI_PROTOTYPE_COMPETITION: false, - WEB_DESIGNS: false, - WIDGET_OR_MOBILE_SCREEN_DESIGN: false, - WIREFRAMES: false, - }) - - function handleChallengeWinModalToggle(subTrack: string): void { - setModalVisibilityMap({ - ...modalVisibilityMap, - [subTrack]: !modalVisibilityMap[subTrack], - }) - } - - return <> - // ( - //
- //
- //
- //

Topcoder Challenge Winner

- //
- // { - // !!props.memberStats.DATA_SCIENCE?.SRM?.wins && ( - // - // ) - // } - // { - // !!props.memberStats.DATA_SCIENCE?.MARATHON_MATCH?.wins && ( - // - // ) - // } - // { - // !!props.memberStats.DEVELOP?.wins - // && props.memberStats.DEVELOP?.subTracks.map((ms: MemberStats) => (ms.wins ? ( - // - // ) : undefined)) - // } - // { - // !!props.memberStats.DESIGN?.wins - // && props.memberStats.DESIGN?.subTracks.map((ms: MemberStats) => (ms.wins ? ( - // - // ) : undefined)) - // } - // { - // !!props.memberStats.COPILOT && ( - // - // ) - // } - //
- //

- // Topcoder challenges are open competitions where community - // members participate in small units of work to deliver projects. - //

- //
- //
- - // {modalVisibilityMap.SRM && ( - // - // )} - - // {modalVisibilityMap.DS && ( - // - // )} - - // {modalVisibilityMap.CODE && ( - // - // )} - - // {modalVisibilityMap.FIRST_2_FINISH && ( - // - // )} - - // {modalVisibilityMap.ASSEMBLY_COMPETITION && ( - // - // )} - - // {modalVisibilityMap.CONTENT_CREATION && ( - // - // )} - - // {modalVisibilityMap.UI_PROTOTYPE_COMPETITION && ( - // - // )} - - // {modalVisibilityMap.WEB_DESIGNS && ( - // - // )} - - // {modalVisibilityMap.LOGO_DESIGN && ( - // - // )} - - // {modalVisibilityMap.DESIGN_FIRST_2_FINISH && ( - // - // )} - - // {modalVisibilityMap.TEST_SCENARIOS && ( - // - // )} - - // {modalVisibilityMap.BUG_HUNT && ( - // - // )} - - // {modalVisibilityMap.COPILOT && ( - // - // )} - - // {modalVisibilityMap.WIREFRAMES && ( - // - // )} - - // {modalVisibilityMap.FRONT_END_FLASH && ( - // - // )} - - // {modalVisibilityMap.PRINT_OR_PRESENTATION && ( - // - // )} - - // {modalVisibilityMap.STUDIO_OTHER && ( - // - // )} - - // {modalVisibilityMap.APPLICATION_FRONT_END_DESIGN && ( - // - // )} - - // {modalVisibilityMap.BANNERS_OR_ICONS && ( - // - // )} - - // {modalVisibilityMap.WIDGET_OR_MOBILE_SCREEN_DESIGN && ( - // - // )} - - // {modalVisibilityMap.TEST_SUITES && ( - // - // )} - - // {modalVisibilityMap.SPECIFICATION && ( - // - // )} - - // {modalVisibilityMap.DEVELOPMENT && ( - // - // )} - - // {modalVisibilityMap.ARCHITECTURE && ( - // - // )} - - // {modalVisibilityMap.COPILOT_POSTING && ( - // - // )} - - // {modalVisibilityMap.DESIGN && ( - // - // )} - - // {modalVisibilityMap.CONCEPTUALIZATION && ( - // - // )} - - //
- // ) -} - -export default ChallengeWinsBanner diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/index.ts b/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/index.ts deleted file mode 100644 index fcb50d371..000000000 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeWinsBanner/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as ChallengeWinsBanner } from './ChallengeWinsBanner' diff --git a/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.module.scss b/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.module.scss new file mode 100644 index 000000000..47c7f6b64 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.module.scss @@ -0,0 +1,25 @@ +@import '@libs/ui/styles/includes'; + +.chartTitle { + font-size: 18px; + line-height: 24px; + font-family: $font-roboto; + text-align: center; + text-transform: uppercase; + margin: $sp-9 0 $sp-6; +} + +.btnsBar { + display: flex; + .btnsGroup.toRight { + margin-left: auto; + } +} + +.btnsGroup { + display: flex; + margin-bottom: $sp-4; + .btn { + border-radius: 0; + } +} diff --git a/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.tsx b/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.tsx new file mode 100644 index 000000000..21c5d210d --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.tsx @@ -0,0 +1,124 @@ +import { FC, ReactNode, useState } from 'react' +import AnnotationsModule from 'highcharts/modules/annotations' +import Highcharts from 'highcharts' +import HighchartsReact from 'highcharts-react-official' +import classNames from 'classnames' + +import { + MemberStats, + SRMStats, + StatsHistory, + UserStatsDistributionResponse, +} from '~/libs/core' +import { Button } from '~/libs/ui' + +import { useRatingDistroOptions, useRatingHistoryOptions } from '../../../hooks' +import { subTrackLabelToHumanName } from '../../../lib' + +import styles from './DetailedTrackView.module.scss' + +export enum ViewMode { + statistics = 'statistics', + details = 'details', +} + +enum Graphs { + distribution = 'distribution', + history = 'history', +} + +interface DetailedTrackViewProps { + trackData?: SRMStats | MemberStats + trackHistory?: StatsHistory[] + ratingDistribution?: UserStatsDistributionResponse + showDetailsViewBtn?: boolean + challengesDetailedView?: ReactNode + defaultViewMode?: ViewMode +} + +AnnotationsModule(Highcharts) + +const DetailedTrackView: FC = props => { + const [activeGraph, setActiveGraph] = useState(Graphs.distribution) + const [viewMode, setViewMode] = useState(props.defaultViewMode ?? ViewMode.details) + + const trackName: string = (props.trackData as MemberStats).name ?? 'SRM' + + const ratingHistoryOptions: Highcharts.Options | undefined + = useRatingHistoryOptions( + props.trackHistory, + `${subTrackLabelToHumanName(trackName)} Rating`, + ) + + const ratingDistributionOptions: Highcharts.Options | undefined + = useRatingDistroOptions(props.ratingDistribution?.distribution || {}, props.trackData?.rank?.rating ?? 0) + + return ( +
+
+ {viewMode === ViewMode.statistics && ( +
+
+ )} + {props.showDetailsViewBtn && ( +
+
+ )} +
+ + {viewMode === ViewMode.details && props.challengesDetailedView} + + {viewMode === ViewMode.statistics && ( + <> + {activeGraph === Graphs.history && ratingHistoryOptions && ( + + )} + + {activeGraph === Graphs.distribution && ratingDistributionOptions && ( + + )} + + )} +
+ ) +} + +export default DetailedTrackView diff --git a/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/index.ts b/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/index.ts new file mode 100644 index 000000000..dee89b335 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/index.ts @@ -0,0 +1 @@ +export { default as DetailedTrackView } from './DetailedTrackView' diff --git a/src/apps/profiles/src/components/tc-achievements/DevelopTrackView/DevelopTrackView.module.scss b/src/apps/profiles/src/components/tc-achievements/DevelopTrackView/DevelopTrackView.module.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/apps/profiles/src/components/tc-achievements/DevelopTrackView/DevelopTrackView.tsx b/src/apps/profiles/src/components/tc-achievements/DevelopTrackView/DevelopTrackView.tsx new file mode 100644 index 000000000..86cec5eb8 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/DevelopTrackView/DevelopTrackView.tsx @@ -0,0 +1,49 @@ +import { FC, useMemo } from 'react' +import { find, get, isEmpty } from 'lodash' + +import { + MemberStats, + UserProfile, + UserStatsDistributionResponse, + UserStatsHistory, + useStatsDistribution, + useStatsHistory, +} from '~/libs/core' + +import { DetailedTrackView } from '../DetailedTrackView' +import { ChallengeHistoryView } from '../ChallengeHistoryView' + +interface DevelopTrackViewProps { + profile: UserProfile + trackData: MemberStats +} + +const DevelopTrackView: FC = props => { + const statsHistory: UserStatsHistory | undefined = useStatsHistory(props.profile?.handle) + + const trackName: string = (props.trackData as MemberStats).name ?? 'SRM' + const trackHistory = get(find(get(statsHistory, `${props.trackData.path}`), { name: trackName }), 'history') + + const ratingDistribution: UserStatsDistributionResponse | undefined = useStatsDistribution({ + filter: `track=${props.trackData.parentTrack}&subTrack=${trackName}`, + }) + + const showDetailsViewBtn = useMemo(() => ( + (!!ratingDistribution && !isEmpty(ratingDistribution)) + || (!!trackHistory && !isEmpty(trackHistory)) + ), [ratingDistribution, trackHistory]) + + return ( + + )} + /> + ) +} + +export default DevelopTrackView diff --git a/src/apps/profiles/src/components/tc-achievements/DevelopTrackView/index.ts b/src/apps/profiles/src/components/tc-achievements/DevelopTrackView/index.ts new file mode 100644 index 000000000..f36d85541 --- /dev/null +++ b/src/apps/profiles/src/components/tc-achievements/DevelopTrackView/index.ts @@ -0,0 +1 @@ +export { default as DevelopTrackView } from './DevelopTrackView' diff --git a/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.module.scss b/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.module.scss index e5d8df91c..e69de29bb 100644 --- a/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.module.scss +++ b/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.module.scss @@ -1,18 +0,0 @@ -@import '@libs/ui/styles/includes'; - -.chartTitle { - font-size: 18px; - line-height: 24px; - font-family: $font-roboto; - text-align: center; - text-transform: uppercase; - margin: $sp-9 0 $sp-6; -} - -.btnsGroup { - display: flex; - margin-bottom: $sp-4; - .btn { - border-radius: 0; - } -} diff --git a/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx b/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx index a0af15b2e..f863927b3 100644 --- a/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx +++ b/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx @@ -1,8 +1,5 @@ -import { FC, useState } from 'react' +import { FC, useMemo } from 'react' import { get } from 'lodash' -import AnnotationsModule from 'highcharts/modules/annotations' -import Highcharts from 'highcharts' -import HighchartsReact from 'highcharts-react-official' import { MemberStats, @@ -13,9 +10,11 @@ import { useStatsDistribution, useStatsHistory, } from '~/libs/core' -import { Button } from '~/libs/ui' -import { useRatingDistroOptions, useRatingHistoryOptions } from '../../../hooks' +import { DetailedTrackView } from '../DetailedTrackView' +import { DivisionGrid } from '../../DivisionGrid' +import { ChallengesGrid } from '../../ChallengesGrid' +import { ViewMode } from '../DetailedTrackView/DetailedTrackView' import styles from './SRMView.module.scss' @@ -24,69 +23,45 @@ interface SRMViewProps { trackData: SRMStats | MemberStats } -AnnotationsModule(Highcharts) - -enum Graphs { - distribution = 'distribution', - history = 'history', -} - const SRMView: FC = props => { - const [activeGraph, setActiveGraph] = useState(Graphs.distribution) const statsHistory: UserStatsHistory | undefined = useStatsHistory(props.profile?.handle) const trackName: string = (props.trackData as MemberStats).name ?? 'SRM' - const ratingHistoryOptions: Highcharts.Options | undefined - = useRatingHistoryOptions( - get(statsHistory, `DATA_SCIENCE.${trackName}.history`), - `${trackName} Rating`, - 'date', - 'rating', - ) + const trackHistory = get(statsHistory, `${props.trackData.path}.${trackName}.history`) - const memberStatsDist: UserStatsDistributionResponse | undefined = useStatsDistribution({ - filter: `track=DATA_SCIENCE&subTrack=${trackName}`, + const ratingDistribution: UserStatsDistributionResponse | undefined = useStatsDistribution({ + filter: `track=${props.trackData.parentTrack}&subTrack=${trackName}`, }) - const ratingDistributionOptions: Highcharts.Options | undefined - = useRatingDistroOptions(memberStatsDist?.distribution || {}, props.trackData.rank.rating) + const showDetailsViewBtn = useMemo(() => Boolean( + (props.trackData as SRMStats)?.division1 + || (props.trackData as SRMStats)?.division2 + || (props.trackData as SRMStats)?.challengeDetails, + ), [props.trackData]) return ( -
-
-
- {activeGraph === Graphs.history && ratingHistoryOptions && ( - - )} - - {activeGraph === Graphs.distribution && ratingDistributionOptions && ( - + +
+ {(props.trackData as SRMStats)?.division1 && ( + + )} + {(props.trackData as SRMStats)?.division2 && ( + + )} + {(props.trackData as SRMStats)?.challengeDetails && ( + + )} +
+ )} -
+ /> ) } diff --git a/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx index 961f4b736..e59017e8e 100644 --- a/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx +++ b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx @@ -37,6 +37,8 @@ const StatsDetailsLayout: FC = props => ( ranking={(props.trackData as MemberStatsTrack).ranking} rating={(props.trackData as MemberStats).rank?.rating} volatility={(props.trackData as MemberStats).rank?.volatility} + screeningSuccessRate={(props.trackData as MemberStats).screeningSuccessRate} + percentile={(props.trackData as MemberStats).rank?.overallPercentile} />
{props.children} diff --git a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx index 4cc3ffb34..c88a29529 100644 --- a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx +++ b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx @@ -2,6 +2,8 @@ import { FC } from 'react' import { getRatingColor } from '~/libs/core' +import { numberToFixed } from '../../../lib' + import styles from './StatsSummaryBlock.module.scss' interface StatsSummaryBlockProps { @@ -11,6 +13,8 @@ interface StatsSummaryBlockProps { submissions?: number ranking?: number rating?: number + percentile?: number + screeningSuccessRate?: number volatility?: number } @@ -58,7 +62,7 @@ const StatsSummaryBlock: FC = props => ( {props.ranking !== undefined && (
- {props.ranking} + {numberToFixed(props.ranking)} % @@ -91,6 +95,28 @@ const StatsSummaryBlock: FC = props => (
)} + {props.screeningSuccessRate !== undefined && ( +
+ + {numberToFixed(props.screeningSuccessRate * 100)} + % + + + Screening Success Rate + +
+ )} + {props.percentile !== undefined && ( +
+ + {numberToFixed(props.percentile)} + % + + + Percentile + +
+ )}
) diff --git a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx index 48c7db343..9418ef6b2 100644 --- a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx +++ b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx @@ -37,27 +37,37 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => MARATHON_MATCH: (memberStats?.DATA_SCIENCE?.MARATHON_MATCH && ({ ...memberStats.DATA_SCIENCE.MARATHON_MATCH, name: 'MARATHON_MATCH', - parent: 'DATA_SCIENCE', + parentTrack: 'DATA_SCIENCE', + path: 'DATA_SCIENCE', })) as MemberStats, SRM: (memberStats?.DATA_SCIENCE?.SRM && ({ ...memberStats.DATA_SCIENCE.SRM, name: 'SRM', - parent: 'DATA_SCIENCE', + parentTrack: 'DATA_SCIENCE', + path: 'DATA_SCIENCE', })) as SRMStats & {name: string}, }), [memberStats]) // Create mappings for the subtracks, by the subtrack name, so we can easily access it later on const designSubTracks: {[key: string]: MemberStats} = useMemo(() => ( memberStats?.DESIGN?.subTracks.reduce((all, subTrack) => { - all[subTrack.name] = { ...subTrack, parent: 'DESIGN.subTracks' } + all[subTrack.name] = { + ...subTrack, + parentTrack: 'DESIGN', + path: 'DESIGN.subTracks', + } return all }, {} as {[key: string]: MemberStats}) ?? {} ), [memberStats]) const developSubTracks: {[key: string]: MemberStats} = useMemo(() => ( memberStats?.DEVELOP?.subTracks.reduce((all, subTrack) => { - all[subTrack.name] = { ...subTrack, parent: 'DEVELOP.subTracks' } + all[subTrack.name] = { + ...subTrack, + parentTrack: 'DEVELOP', + path: 'DEVELOP.subTracks', + } return all }, {} as {[key: string]: MemberStats}) ?? {} ), [memberStats]) @@ -121,16 +131,6 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => } }, [dataScienceSubTracks, memberStats]) - // copilot - // const copilotTrackStats = useMemo(() => ({ - // isActive: (memberStats?.DATA_SCIENCE?.challenges ?? 0) > 0, - // ranking: Math.max( - // memberStats?.DATA_SCIENCE?.MARATHON_MATCH.rank.percentile ?? 0, - // memberStats?.DATA_SCIENCE?.SRM.rank.percentile ?? 0, - // ), - // wins: memberStats?.DATA_SCIENCE?.wins ?? 0, - // }), [memberStats]) - return orderBy(filter([ cpTrackStats, designTrackStats, diff --git a/src/apps/profiles/src/hooks/useRatingDistroOptions.tsx b/src/apps/profiles/src/hooks/useRatingDistroOptions.tsx index 08e9c1ea1..e33d1dfbb 100644 --- a/src/apps/profiles/src/hooks/useRatingDistroOptions.tsx +++ b/src/apps/profiles/src/hooks/useRatingDistroOptions.tsx @@ -1,5 +1,5 @@ import { useMemo } from 'react' -import { isEmpty, toPairs } from 'lodash' +import { cloneDeep, isEmpty, toPairs } from 'lodash' import AnnotationsModule from 'highcharts/modules/annotations' import Highcharts from 'highcharts' @@ -95,7 +95,7 @@ export function useRatingDistroOptions( memberRating: number | undefined, ): Highcharts.Options | undefined { const ratingDistributionOptions: Highcharts.Options | undefined = useMemo(() => { - const options: Highcharts.Options = RATING_DISTRO_CHART_CONFIG + const options: Highcharts.Options = cloneDeep(RATING_DISTRO_CHART_CONFIG) const ranges = getNonZeroRanges(getRanges(ratingDistro)) if (isEmpty(ratingDistro)) return undefined diff --git a/src/apps/profiles/src/hooks/useRatingHistoryOptions.tsx b/src/apps/profiles/src/hooks/useRatingHistoryOptions.tsx index 46d820b24..78150e8ad 100644 --- a/src/apps/profiles/src/hooks/useRatingHistoryOptions.tsx +++ b/src/apps/profiles/src/hooks/useRatingHistoryOptions.tsx @@ -1,5 +1,5 @@ import { useMemo } from 'react' -import { get } from 'lodash' +import { cloneDeep, get } from 'lodash' import Highcharts from 'highcharts' import { StatsHistory } from '~/libs/core' @@ -30,27 +30,28 @@ export const RATING_CHART_CONFIG: Highcharts.Options = { export function useRatingHistoryOptions( trackHistory: Array | undefined, seriesName: string, - dateField: string = 'ratingDate', - ratingField: string = 'newRating', ): Highcharts.Options | undefined { const ratingHistoryOptions: Highcharts.Options | undefined = useMemo(() => { - const options: Highcharts.Options = RATING_CHART_CONFIG + const options: Highcharts.Options = cloneDeep(RATING_CHART_CONFIG) if (!trackHistory?.length) return undefined + const dateField: string = get(trackHistory[0], 'date') ? 'date' : 'ratingDate' + const ratingField: string = get(trackHistory[0], 'rating') ? 'rating' : 'newRating' + options.series = [{ data: trackHistory.sort((a, b) => get(b, dateField) - get(a, dateField)) - .map((hisChallenge: StatsHistory) => ({ - name: hisChallenge.challengeName, - x: get(hisChallenge, dateField), - y: get(hisChallenge, ratingField), + .map((challenge: StatsHistory) => ({ + name: challenge.challengeName, + x: get(challenge, dateField), + y: get(challenge, ratingField), })), name: seriesName, type: 'spline', }] return options - }, [dateField, ratingField, seriesName, trackHistory]) + }, [seriesName, trackHistory]) return ratingHistoryOptions } diff --git a/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx b/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx index d43d238dd..87f03fc05 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx +++ b/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx @@ -3,9 +3,10 @@ import { useParams } from 'react-router-dom' import { UserProfile } from '~/libs/core' +// import { ChallengeHistoryView } from '../../../components/tc-achievements/ChallengeHistoryView' import { useFetchSubTrackData } from '../../../hooks' import { StatsDetailsLayout } from '../../../components/tc-achievements/StatsDetailsLayout' -import { ChallengeHistoryView } from '../../../components/tc-achievements/ChallengeHistoryView' +import { DevelopTrackView } from '../../../components/tc-achievements/DevelopTrackView' import { getUserProfileRoute, getUserProfileStatsRoute } from '../../../profiles.routes' import { subTrackLabelToHumanName } from '../../../lib' import { SRMView } from '../../../components/tc-achievements/SRMView' @@ -32,10 +33,8 @@ const SubTrackView: FC = props => { > {subTrackData.name === 'MARATHON_MATCH' || subTrackData.name === 'SRM' ? ( - ) : subTrackData.name === 'WEB_DESIGNS' ? ( - 'WEB_DESIGNS test' ) : ( - + )}
diff --git a/src/libs/core/lib/profile/user-stats.model.ts b/src/libs/core/lib/profile/user-stats.model.ts index a99d9ad04..5196fde3a 100644 --- a/src/libs/core/lib/profile/user-stats.model.ts +++ b/src/libs/core/lib/profile/user-stats.model.ts @@ -28,7 +28,8 @@ export type SRMStats = { volatility: number } wins: number - parent?: string + parentTrack?: string + path?: string } export type MemberStats = { @@ -50,7 +51,8 @@ export type MemberStats = { submissions: { submissions: number } - parent?: string + parentTrack?: string + path?: string } export type UserStats = { From 0cb2b6dc322b4329ec0febb6ae110f0421431329 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 21 Nov 2023 10:49:59 +0200 Subject: [PATCH 19/37] MP-356 - remove old details modals, refactor code --- .../AssemblyDetailsModal.module.scss | 36 ---- .../AssemblyDetailsModal.tsx | 158 -------------- .../components/AssemblyDetailsModal/index.ts | 1 - .../BugHuntDetailsModal.module.scss | 36 ---- .../BugHuntDetailsModal.tsx | 73 ------- .../components/BugHuntDetailsModal/index.ts | 1 - .../CodeDetailsModal.module.scss | 36 ---- .../CodeDetailsModal/CodeDetailsModal.tsx | 141 ------------- .../src/components/CodeDetailsModal/index.ts | 1 - .../ContentCreationDetailsModal.module.scss | 36 ---- .../ContentCreationDetailsModal.tsx | 155 -------------- .../ContentCreationDetailsModal/index.ts | 1 - .../CopilotDetailsModal.module.scss | 13 -- .../CopilotDetailsModal.tsx | 48 ----- .../components/CopilotDetailsModal/index.ts | 1 - .../DesignF2FDetailsModal.module.scss | 36 ---- .../DesignF2FDetailsModal.tsx | 86 -------- .../components/DesignF2FDetailsModal/index.ts | 1 - .../F2FDetailsModal.module.scss | 36 ---- .../F2FDetailsModal/F2FDetailsModal.tsx | 73 ------- .../src/components/F2FDetailsModal/index.ts | 1 - .../GenericSubtrackDetailsModal.module.scss | 36 ---- .../GenericSubtrackDetailsModal.tsx | 148 ------------- .../GenericSubtrackDetailsModal/index.ts | 1 - .../LogoDesignDetailsModal.module.scss | 36 ---- .../LogoDesignDetailsModal.tsx | 85 -------- .../LogoDesignDetailsModal/index.ts | 1 - .../MMDetailsModal/MMDetailsModal.module.scss | 36 ---- .../MMDetailsModal/MMDetailsModal.tsx | 150 -------------- .../src/components/MMDetailsModal/index.ts | 1 - .../SRMDetailsModal.module.scss | 42 ---- .../SRMDetailsModal/SRMDetailsModal.tsx | 194 ------------------ .../src/components/SRMDetailsModal/index.ts | 1 - .../TestScenariosDetailsModal.module.scss | 36 ---- .../TestScenariosDetailsModal.tsx | 155 -------------- .../TestScenariosDetailsModal/index.ts | 1 - .../UIPrototypeDetailsModal.module.scss | 36 ---- .../UIPrototypeDetailsModal.tsx | 155 -------------- .../UIPrototypeDetailsModal/index.ts | 1 - .../WebDesignDetailsModal.module.scss | 36 ---- .../WebDesignDetailsModal.tsx | 86 -------- .../components/WebDesignDetailsModal/index.ts | 1 - src/apps/profiles/src/components/index.ts | 18 +- .../ChallengesGrid/ChallengesGrid.module.scss | 0 .../ChallengesGrid/ChallengesGrid.tsx | 0 .../SRMView}/ChallengesGrid/index.ts | 0 .../DivisionGrid/DivisionGrid.module.scss | 0 .../SRMView}/DivisionGrid/DivisionGrid.tsx | 0 .../SRMView}/DivisionGrid/index.ts | 0 .../tc-achievements/SRMView/SRMView.tsx | 4 +- .../src/hooks/useFetchActiveTracks.tsx | 62 +++++- .../src/hooks/useRatingDistroOptions.tsx | 47 +++-- .../src/hooks/useRatingHistoryOptions.tsx | 26 ++- 53 files changed, 119 insertions(+), 2246 deletions(-) delete mode 100644 src/apps/profiles/src/components/AssemblyDetailsModal/AssemblyDetailsModal.module.scss delete mode 100644 src/apps/profiles/src/components/AssemblyDetailsModal/AssemblyDetailsModal.tsx delete mode 100644 src/apps/profiles/src/components/AssemblyDetailsModal/index.ts delete mode 100644 src/apps/profiles/src/components/BugHuntDetailsModal/BugHuntDetailsModal.module.scss delete mode 100644 src/apps/profiles/src/components/BugHuntDetailsModal/BugHuntDetailsModal.tsx delete mode 100644 src/apps/profiles/src/components/BugHuntDetailsModal/index.ts delete mode 100644 src/apps/profiles/src/components/CodeDetailsModal/CodeDetailsModal.module.scss delete mode 100644 src/apps/profiles/src/components/CodeDetailsModal/CodeDetailsModal.tsx delete mode 100644 src/apps/profiles/src/components/CodeDetailsModal/index.ts delete mode 100644 src/apps/profiles/src/components/ContentCreationDetailsModal/ContentCreationDetailsModal.module.scss delete mode 100644 src/apps/profiles/src/components/ContentCreationDetailsModal/ContentCreationDetailsModal.tsx delete mode 100644 src/apps/profiles/src/components/ContentCreationDetailsModal/index.ts delete mode 100644 src/apps/profiles/src/components/CopilotDetailsModal/CopilotDetailsModal.module.scss delete mode 100644 src/apps/profiles/src/components/CopilotDetailsModal/CopilotDetailsModal.tsx delete mode 100644 src/apps/profiles/src/components/CopilotDetailsModal/index.ts delete mode 100644 src/apps/profiles/src/components/DesignF2FDetailsModal/DesignF2FDetailsModal.module.scss delete mode 100644 src/apps/profiles/src/components/DesignF2FDetailsModal/DesignF2FDetailsModal.tsx delete mode 100644 src/apps/profiles/src/components/DesignF2FDetailsModal/index.ts delete mode 100644 src/apps/profiles/src/components/F2FDetailsModal/F2FDetailsModal.module.scss delete mode 100644 src/apps/profiles/src/components/F2FDetailsModal/F2FDetailsModal.tsx delete mode 100644 src/apps/profiles/src/components/F2FDetailsModal/index.ts delete mode 100644 src/apps/profiles/src/components/GenericSubtrackDetailsModal/GenericSubtrackDetailsModal.module.scss delete mode 100644 src/apps/profiles/src/components/GenericSubtrackDetailsModal/GenericSubtrackDetailsModal.tsx delete mode 100644 src/apps/profiles/src/components/GenericSubtrackDetailsModal/index.ts delete mode 100644 src/apps/profiles/src/components/LogoDesignDetailsModal/LogoDesignDetailsModal.module.scss delete mode 100644 src/apps/profiles/src/components/LogoDesignDetailsModal/LogoDesignDetailsModal.tsx delete mode 100644 src/apps/profiles/src/components/LogoDesignDetailsModal/index.ts delete mode 100644 src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.module.scss delete mode 100644 src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.tsx delete mode 100644 src/apps/profiles/src/components/MMDetailsModal/index.ts delete mode 100644 src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.module.scss delete mode 100644 src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.tsx delete mode 100644 src/apps/profiles/src/components/SRMDetailsModal/index.ts delete mode 100644 src/apps/profiles/src/components/TestScenariosDetailsModal/TestScenariosDetailsModal.module.scss delete mode 100644 src/apps/profiles/src/components/TestScenariosDetailsModal/TestScenariosDetailsModal.tsx delete mode 100644 src/apps/profiles/src/components/TestScenariosDetailsModal/index.ts delete mode 100644 src/apps/profiles/src/components/UIPrototypeDetailsModal/UIPrototypeDetailsModal.module.scss delete mode 100644 src/apps/profiles/src/components/UIPrototypeDetailsModal/UIPrototypeDetailsModal.tsx delete mode 100644 src/apps/profiles/src/components/UIPrototypeDetailsModal/index.ts delete mode 100644 src/apps/profiles/src/components/WebDesignDetailsModal/WebDesignDetailsModal.module.scss delete mode 100644 src/apps/profiles/src/components/WebDesignDetailsModal/WebDesignDetailsModal.tsx delete mode 100644 src/apps/profiles/src/components/WebDesignDetailsModal/index.ts rename src/apps/profiles/src/components/{ => tc-achievements/SRMView}/ChallengesGrid/ChallengesGrid.module.scss (100%) rename src/apps/profiles/src/components/{ => tc-achievements/SRMView}/ChallengesGrid/ChallengesGrid.tsx (100%) rename src/apps/profiles/src/components/{ => tc-achievements/SRMView}/ChallengesGrid/index.ts (100%) rename src/apps/profiles/src/components/{ => tc-achievements/SRMView}/DivisionGrid/DivisionGrid.module.scss (100%) rename src/apps/profiles/src/components/{ => tc-achievements/SRMView}/DivisionGrid/DivisionGrid.tsx (100%) rename src/apps/profiles/src/components/{ => tc-achievements/SRMView}/DivisionGrid/index.ts (100%) diff --git a/src/apps/profiles/src/components/AssemblyDetailsModal/AssemblyDetailsModal.module.scss b/src/apps/profiles/src/components/AssemblyDetailsModal/AssemblyDetailsModal.module.scss deleted file mode 100644 index 47e811dd7..000000000 --- a/src/apps/profiles/src/components/AssemblyDetailsModal/AssemblyDetailsModal.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - display: flex; - flex-direction: column; - - .content { - .contentHeader { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid $black-10; - margin: $sp-4 0; - padding-bottom: $sp-2; - - .contentHeaderActions { - button { - margin-right: $sp-2; - - &:last-child { - margin-right: 0; - } - } - } - } - - .contentBody { - height: 385px; - overflow: auto; - - @include ltelg { - height: auto; - } - } - } -} \ No newline at end of file diff --git a/src/apps/profiles/src/components/AssemblyDetailsModal/AssemblyDetailsModal.tsx b/src/apps/profiles/src/components/AssemblyDetailsModal/AssemblyDetailsModal.tsx deleted file mode 100644 index c0e0cde62..000000000 --- a/src/apps/profiles/src/components/AssemblyDetailsModal/AssemblyDetailsModal.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { Dispatch, FC, SetStateAction, useState } from 'react' -import Highcharts from 'highcharts' -import HighchartsReact from 'highcharts-react-official' - -import { BaseModal, LoadingSpinner } from '~/libs/ui' -import { - getRatingColor, - MemberStats, - UserProfile, - UserStatsDistributionResponse, - UserStatsHistory, - useStatsDistribution, - useStatsHistory, -} from '~/libs/core' - -import { numberToFixed } from '../../lib' -import { useRatingDistroOptions, useRatingHistoryOptions } from '../../hooks' - -import styles from './AssemblyDetailsModal.module.scss' - -type SRMViewTypes = 'STATISTICS' | 'CHALLENGES DETAILS' - -interface AssemblyDetailsModalProps { - onClose: () => void - assemblyStats: MemberStats | undefined - profile: UserProfile | undefined -} - -const AssemblyDetailsModal: FC = (props: AssemblyDetailsModalProps) => { - const [viewType, setviewType]: [SRMViewTypes, Dispatch>] - = useState('STATISTICS') - - const statsHistory: UserStatsHistory | undefined = useStatsHistory(props.profile?.handle) - - const ratingHistoryOptions: Highcharts.Options | undefined - = useRatingHistoryOptions( - statsHistory?.DEVELOP?.subTracks?.find(subTrack => subTrack.name === 'ASSEMBLY_COMPETITION')?.history, - 'Assembly Competition Rating', - ) - - const memberStatsDist: UserStatsDistributionResponse | undefined = useStatsDistribution({ - filter: 'track=DEVELOP&subTrack=ASSEMBLY_COMPETITION', - }) - - const ratingDistributionOptions: Highcharts.Options | undefined - = useRatingDistroOptions(memberStatsDist?.distribution || {}, props.assemblyStats?.rank.rating) - - // TODO: enable this function when challenges history is available - // eslint-disable-next-line @typescript-eslint/no-unused-vars - function toggleViewType(newViewType: SRMViewTypes): void { - setviewType(newViewType) - } - - return ( - - - - {!!statsHistory && !!memberStatsDist && ( -
-
-
- - {props.assemblyStats?.rank.rating} - - Rating -
-
- {props.assemblyStats?.rank.overallRank} - Rank -
-
- - {numberToFixed(props.assemblyStats?.rank.overallPercentile || 0)} - % - - Percentile -
-
- {props.assemblyStats?.wins} - Wins -
-
- {props.assemblyStats?.challenges} - Challenges -
-
- -
- {/* TODO: Add Assembly Details with challenges history */} - {/*
-

{viewType}

-
- -
-
*/} - -
-

{viewType}

-
- -
- { - viewType === 'STATISTICS' && ( -
- { - ratingHistoryOptions && ( - - ) - } - { - ratingDistributionOptions && ( - - ) - } -
- ) - - } - { - viewType === 'CHALLENGES DETAILS' && ( -
- ) - - } -
-
-
- )} - - ) -} - -export default AssemblyDetailsModal diff --git a/src/apps/profiles/src/components/AssemblyDetailsModal/index.ts b/src/apps/profiles/src/components/AssemblyDetailsModal/index.ts deleted file mode 100644 index 3a0454769..000000000 --- a/src/apps/profiles/src/components/AssemblyDetailsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as AssemblyDetailsModal } from './AssemblyDetailsModal' diff --git a/src/apps/profiles/src/components/BugHuntDetailsModal/BugHuntDetailsModal.module.scss b/src/apps/profiles/src/components/BugHuntDetailsModal/BugHuntDetailsModal.module.scss deleted file mode 100644 index 47e811dd7..000000000 --- a/src/apps/profiles/src/components/BugHuntDetailsModal/BugHuntDetailsModal.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - display: flex; - flex-direction: column; - - .content { - .contentHeader { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid $black-10; - margin: $sp-4 0; - padding-bottom: $sp-2; - - .contentHeaderActions { - button { - margin-right: $sp-2; - - &:last-child { - margin-right: 0; - } - } - } - } - - .contentBody { - height: 385px; - overflow: auto; - - @include ltelg { - height: auto; - } - } - } -} \ No newline at end of file diff --git a/src/apps/profiles/src/components/BugHuntDetailsModal/BugHuntDetailsModal.tsx b/src/apps/profiles/src/components/BugHuntDetailsModal/BugHuntDetailsModal.tsx deleted file mode 100644 index 3531242c6..000000000 --- a/src/apps/profiles/src/components/BugHuntDetailsModal/BugHuntDetailsModal.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* eslint-disable complexity */ -import { Dispatch, FC, SetStateAction, useState } from 'react' - -import { BaseModal } from '~/libs/ui' -import { - MemberStats, -} from '~/libs/core' - -import { numberToFixed } from '../../lib' - -import styles from './BugHuntDetailsModal.module.scss' - -type BugHuntViewTypes = 'CHALLENGES DETAILS' - -interface BugHuntDetailsModalProps { - onClose: () => void - bugHuntStats: MemberStats | undefined -} - -const BugHuntDetailsModal: FC = (props: BugHuntDetailsModalProps) => { - // TODO: Add Bug Hunt Details with challenges history - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [viewType]: [BugHuntViewTypes, Dispatch>] - = useState('CHALLENGES DETAILS') - - return ( - - -
-
-
- - {numberToFixed(props.bugHuntStats?.rank.overallPercentile || 0)} - % - - Percentile -
-
- {props.bugHuntStats?.wins} - Wins -
-
- {props.bugHuntStats?.challenges} - Challenges -
-
- - {/* TODO: Add Bug Hunt Details with challenges history */} - {/*
-
-

{viewType}

-
- -
- { - viewType === 'CHALLENGES DETAILS' && ( -
- ) - - } -
-
*/} -
- - ) -} - -export default BugHuntDetailsModal diff --git a/src/apps/profiles/src/components/BugHuntDetailsModal/index.ts b/src/apps/profiles/src/components/BugHuntDetailsModal/index.ts deleted file mode 100644 index c3df0c89d..000000000 --- a/src/apps/profiles/src/components/BugHuntDetailsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as BugHuntDetailsModal } from './BugHuntDetailsModal' diff --git a/src/apps/profiles/src/components/CodeDetailsModal/CodeDetailsModal.module.scss b/src/apps/profiles/src/components/CodeDetailsModal/CodeDetailsModal.module.scss deleted file mode 100644 index 47e811dd7..000000000 --- a/src/apps/profiles/src/components/CodeDetailsModal/CodeDetailsModal.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - display: flex; - flex-direction: column; - - .content { - .contentHeader { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid $black-10; - margin: $sp-4 0; - padding-bottom: $sp-2; - - .contentHeaderActions { - button { - margin-right: $sp-2; - - &:last-child { - margin-right: 0; - } - } - } - } - - .contentBody { - height: 385px; - overflow: auto; - - @include ltelg { - height: auto; - } - } - } -} \ No newline at end of file diff --git a/src/apps/profiles/src/components/CodeDetailsModal/CodeDetailsModal.tsx b/src/apps/profiles/src/components/CodeDetailsModal/CodeDetailsModal.tsx deleted file mode 100644 index a51bde1c7..000000000 --- a/src/apps/profiles/src/components/CodeDetailsModal/CodeDetailsModal.tsx +++ /dev/null @@ -1,141 +0,0 @@ -/* eslint-disable complexity */ -import { Dispatch, FC, SetStateAction, useState } from 'react' -import Highcharts from 'highcharts' -import HighchartsReact from 'highcharts-react-official' - -import { BaseModal, LoadingSpinner } from '~/libs/ui' -import { - MemberStats, - UserProfile, - UserStatsDistributionResponse, - UserStatsHistory, - useStatsDistribution, - useStatsHistory, -} from '~/libs/core' - -import { numberToFixed } from '../../lib' -import { useRatingDistroOptions, useRatingHistoryOptions } from '../../hooks' - -import styles from './CodeDetailsModal.module.scss' - -type CodeViewTypes = 'STATISTICS' | 'CHALLENGES DETAILS' - -interface CodeDetailsModalProps { - onClose: () => void - codeStats: MemberStats | undefined - profile: UserProfile | undefined -} - -const CodeDetailsModal: FC = (props: CodeDetailsModalProps) => { - const [viewType, setviewType]: [CodeViewTypes, Dispatch>] - = useState('STATISTICS') - - const statsHistory: UserStatsHistory | undefined = useStatsHistory(props.profile?.handle) - - const ratingHistoryOptions: Highcharts.Options | undefined - = useRatingHistoryOptions( - statsHistory?.DEVELOP?.subTracks?.find(subTrack => subTrack.name === 'CODE')?.history, - 'Code Rating', - ) - - const memberStatsDist: UserStatsDistributionResponse | undefined = useStatsDistribution({ - filter: 'track=DEVELOP&subTrack=CODE', - }) - - const ratingDistributionOptions: Highcharts.Options | undefined - = useRatingDistroOptions(memberStatsDist?.distribution || {}, props.codeStats?.rank.rating) - - // TODO: Enable this when we have challenges details data - // eslint-disable-next-line @typescript-eslint/no-unused-vars - function toggleViewType(newViewType: CodeViewTypes): void { - setviewType(newViewType) - } - - return ( - - - - {!!statsHistory && !!memberStatsDist && ( -
-
-
- - {numberToFixed(props.codeStats?.rank.overallPercentile || 0)} - % - - Percentile -
-
- {props.codeStats?.wins} - Wins -
-
- {props.codeStats?.challenges} - Challenges -
-
- -
-
-

{viewType}

- {/* TODO: Add button when we have challenges details data */} - {/*
- -
*/} -
- -
- { - viewType === 'STATISTICS' && ( -
- { - ratingHistoryOptions && ( - - ) - } - { - ratingDistributionOptions && ( - - ) - } -
- ) - - } - { - viewType === 'CHALLENGES DETAILS' && ( -
- ) - - } -
-
-
- )} - - ) -} - -export default CodeDetailsModal diff --git a/src/apps/profiles/src/components/CodeDetailsModal/index.ts b/src/apps/profiles/src/components/CodeDetailsModal/index.ts deleted file mode 100644 index 2df54fa43..000000000 --- a/src/apps/profiles/src/components/CodeDetailsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as CodeDetailsModal } from './CodeDetailsModal' diff --git a/src/apps/profiles/src/components/ContentCreationDetailsModal/ContentCreationDetailsModal.module.scss b/src/apps/profiles/src/components/ContentCreationDetailsModal/ContentCreationDetailsModal.module.scss deleted file mode 100644 index 47e811dd7..000000000 --- a/src/apps/profiles/src/components/ContentCreationDetailsModal/ContentCreationDetailsModal.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - display: flex; - flex-direction: column; - - .content { - .contentHeader { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid $black-10; - margin: $sp-4 0; - padding-bottom: $sp-2; - - .contentHeaderActions { - button { - margin-right: $sp-2; - - &:last-child { - margin-right: 0; - } - } - } - } - - .contentBody { - height: 385px; - overflow: auto; - - @include ltelg { - height: auto; - } - } - } -} \ No newline at end of file diff --git a/src/apps/profiles/src/components/ContentCreationDetailsModal/ContentCreationDetailsModal.tsx b/src/apps/profiles/src/components/ContentCreationDetailsModal/ContentCreationDetailsModal.tsx deleted file mode 100644 index 56df1f9ec..000000000 --- a/src/apps/profiles/src/components/ContentCreationDetailsModal/ContentCreationDetailsModal.tsx +++ /dev/null @@ -1,155 +0,0 @@ -/* eslint-disable complexity */ -import { Dispatch, FC, SetStateAction, useState } from 'react' -import Highcharts from 'highcharts' -import HighchartsReact from 'highcharts-react-official' - -import { BaseModal, LoadingSpinner } from '~/libs/ui' -import { - getRatingColor, - MemberStats, - UserProfile, - UserStatsDistributionResponse, - UserStatsHistory, - useStatsDistribution, - useStatsHistory, -} from '~/libs/core' - -import { numberToFixed } from '../../lib' -import { useRatingDistroOptions, useRatingHistoryOptions } from '../../hooks' - -import styles from './ContentCreationDetailsModal.module.scss' - -type TestScenViewTypes = 'STATISTICS' | 'CHALLENGES DETAILS' - -interface ContentCreationDetailsModalProps { - onClose: () => void - contentCreationStats: MemberStats | undefined - profile: UserProfile | undefined -} - -const ContentCreationDetailsModal: FC = (props: ContentCreationDetailsModalProps) => { - const [viewType, setviewType]: [TestScenViewTypes, Dispatch>] - = useState('STATISTICS') - - const statsHistory: UserStatsHistory | undefined = useStatsHistory(props.profile?.handle) - - const ratingHistoryOptions: Highcharts.Options | undefined - = useRatingHistoryOptions( - statsHistory?.DEVELOP?.subTracks?.find(subTrack => subTrack.name === 'CONTENT_CREATION')?.history, - 'Content Creation Rating', - ) - - const memberStatsDist: UserStatsDistributionResponse | undefined = useStatsDistribution({ - filter: 'track=DEVELOP&subTrack=CONTENT_CREATION', - }) - - const ratingDistributionOptions: Highcharts.Options | undefined - = useRatingDistroOptions(memberStatsDist?.distribution || {}, props.contentCreationStats?.rank.rating) - - // TODO: Enable this when we have challenges details data - // eslint-disable-next-line @typescript-eslint/no-unused-vars - function toggleViewType(newViewType: TestScenViewTypes): void { - setviewType(newViewType) - } - - return ( - - - - {!!statsHistory && !!memberStatsDist && ( -
-
-
- - {props.contentCreationStats?.rank.rating} - - Rating -
-
- {props.contentCreationStats?.rank.overallRank} - Rank -
-
- - {numberToFixed(props.contentCreationStats?.rank.overallPercentile || 0)} - % - - Percentile -
-
- {props.contentCreationStats?.wins} - Wins -
-
- {props.contentCreationStats?.challenges} - Challenges -
-
- -
-
-

{viewType}

- {/* TODO: Enable this when we have challenges details data */} - {/*
- -
*/} -
- -
- { - viewType === 'STATISTICS' && ( -
- { - ratingHistoryOptions && ( - - ) - } - { - ratingDistributionOptions && ( - - ) - } -
- ) - - } - { - viewType === 'CHALLENGES DETAILS' && ( -
- ) - - } -
-
-
- )} - - ) -} - -export default ContentCreationDetailsModal diff --git a/src/apps/profiles/src/components/ContentCreationDetailsModal/index.ts b/src/apps/profiles/src/components/ContentCreationDetailsModal/index.ts deleted file mode 100644 index 888cd4525..000000000 --- a/src/apps/profiles/src/components/ContentCreationDetailsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as ContentCreationDetailsModal } from './ContentCreationDetailsModal' diff --git a/src/apps/profiles/src/components/CopilotDetailsModal/CopilotDetailsModal.module.scss b/src/apps/profiles/src/components/CopilotDetailsModal/CopilotDetailsModal.module.scss deleted file mode 100644 index 7f2e13849..000000000 --- a/src/apps/profiles/src/components/CopilotDetailsModal/CopilotDetailsModal.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - display: grid; - grid-template-columns: 1fr 1fr; - row-gap: $sp-4; - column-gap: $sp-8; - - .contentItem { - display: flex; - align-items: flex-end; - } -} diff --git a/src/apps/profiles/src/components/CopilotDetailsModal/CopilotDetailsModal.tsx b/src/apps/profiles/src/components/CopilotDetailsModal/CopilotDetailsModal.tsx deleted file mode 100644 index cf5742221..000000000 --- a/src/apps/profiles/src/components/CopilotDetailsModal/CopilotDetailsModal.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { FC } from 'react' - -import { BaseModal } from '~/libs/ui' -import { UserStats } from '~/libs/core' - -import styles from './CopilotDetailsModal.module.scss' - -interface CopilotDetailsModalProps { - onClose: () => void - copilotDetails: UserStats['COPILOT'] -} - -const CopilotDetailsModal: FC = (props: CopilotDetailsModalProps) => ( - -
-
- {props.copilotDetails?.activeContests} - Active Challenges -
-
- {props.copilotDetails?.activeProjects} - Active Projects -
-
- {props.copilotDetails?.contests} - Total Challenges -
-
- {props.copilotDetails?.projects} - Total Projects -
-
- - {props.copilotDetails?.fulfillment} - % - - Fulfillment -
-
-
-) - -export default CopilotDetailsModal diff --git a/src/apps/profiles/src/components/CopilotDetailsModal/index.ts b/src/apps/profiles/src/components/CopilotDetailsModal/index.ts deleted file mode 100644 index 3f70c2a99..000000000 --- a/src/apps/profiles/src/components/CopilotDetailsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as CopilotDetailsModal } from './CopilotDetailsModal' diff --git a/src/apps/profiles/src/components/DesignF2FDetailsModal/DesignF2FDetailsModal.module.scss b/src/apps/profiles/src/components/DesignF2FDetailsModal/DesignF2FDetailsModal.module.scss deleted file mode 100644 index 47e811dd7..000000000 --- a/src/apps/profiles/src/components/DesignF2FDetailsModal/DesignF2FDetailsModal.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - display: flex; - flex-direction: column; - - .content { - .contentHeader { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid $black-10; - margin: $sp-4 0; - padding-bottom: $sp-2; - - .contentHeaderActions { - button { - margin-right: $sp-2; - - &:last-child { - margin-right: 0; - } - } - } - } - - .contentBody { - height: 385px; - overflow: auto; - - @include ltelg { - height: auto; - } - } - } -} \ No newline at end of file diff --git a/src/apps/profiles/src/components/DesignF2FDetailsModal/DesignF2FDetailsModal.tsx b/src/apps/profiles/src/components/DesignF2FDetailsModal/DesignF2FDetailsModal.tsx deleted file mode 100644 index 133f9bd96..000000000 --- a/src/apps/profiles/src/components/DesignF2FDetailsModal/DesignF2FDetailsModal.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* eslint-disable complexity */ -import { Dispatch, FC, SetStateAction, useState } from 'react' - -import { BaseModal } from '~/libs/ui' -import { - MemberStats, -} from '~/libs/core' - -import { numberToFixed } from '../../lib' - -import styles from './DesignF2FDetailsModal.module.scss' - -type WebDesignViewTypes = 'CHALLENGES DETAILS' - -interface DesignF2FDetailsModalProps { - onClose: () => void - designF2FStats: MemberStats | undefined -} - -const DesignF2FDetailsModal: FC = (props: DesignF2FDetailsModalProps) => { - // TODO: Enable this when we have challenges details data - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [viewType]: [WebDesignViewTypes, Dispatch>] - = useState('CHALLENGES DETAILS') - - return ( - - -
-
-
- {props.designF2FStats?.wins} - Wins -
-
- {props.designF2FStats?.challenges} - Challenges -
-
- - {numberToFixed(props.designF2FStats?.rank?.overallPercentile || 0)} - % - - Percentile -
-
- - {numberToFixed(props.designF2FStats?.screeningSuccessRate || 0)} - % - - Screening Success Rate -
-
- - {numberToFixed(props.designF2FStats?.avgPlacement || 0)} - - Average Placement -
-
- - {/* TODO: Enable this when we have challenges details data */} - {/*
-
-

{viewType}

-
- -
- { - viewType === 'CHALLENGES DETAILS' && ( -
- ) - - } -
-
*/} -
- - ) -} - -export default DesignF2FDetailsModal diff --git a/src/apps/profiles/src/components/DesignF2FDetailsModal/index.ts b/src/apps/profiles/src/components/DesignF2FDetailsModal/index.ts deleted file mode 100644 index e2f5a6a0f..000000000 --- a/src/apps/profiles/src/components/DesignF2FDetailsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as DesignF2FDetailsModal } from './DesignF2FDetailsModal' diff --git a/src/apps/profiles/src/components/F2FDetailsModal/F2FDetailsModal.module.scss b/src/apps/profiles/src/components/F2FDetailsModal/F2FDetailsModal.module.scss deleted file mode 100644 index 47e811dd7..000000000 --- a/src/apps/profiles/src/components/F2FDetailsModal/F2FDetailsModal.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - display: flex; - flex-direction: column; - - .content { - .contentHeader { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid $black-10; - margin: $sp-4 0; - padding-bottom: $sp-2; - - .contentHeaderActions { - button { - margin-right: $sp-2; - - &:last-child { - margin-right: 0; - } - } - } - } - - .contentBody { - height: 385px; - overflow: auto; - - @include ltelg { - height: auto; - } - } - } -} \ No newline at end of file diff --git a/src/apps/profiles/src/components/F2FDetailsModal/F2FDetailsModal.tsx b/src/apps/profiles/src/components/F2FDetailsModal/F2FDetailsModal.tsx deleted file mode 100644 index d89d66576..000000000 --- a/src/apps/profiles/src/components/F2FDetailsModal/F2FDetailsModal.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* eslint-disable complexity */ -import { Dispatch, FC, SetStateAction, useState } from 'react' - -import { BaseModal } from '~/libs/ui' -import { - MemberStats, -} from '~/libs/core' - -import { numberToFixed } from '../../lib' - -import styles from './F2FDetailsModal.module.scss' - -type BugHuntViewTypes = 'CHALLENGES DETAILS' - -interface F2FDetailsModalProps { - onClose: () => void - f2fStats: MemberStats | undefined -} - -const F2FDetailsModal: FC = (props: F2FDetailsModalProps) => { - // TODO: Add F2F details data - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [viewType]: [BugHuntViewTypes, Dispatch>] - = useState('CHALLENGES DETAILS') - - return ( - - -
-
-
- - {numberToFixed(props.f2fStats?.rank.overallPercentile || 0)} - % - - Percentile -
-
- {props.f2fStats?.wins} - Wins -
-
- {props.f2fStats?.challenges} - Challenges -
-
- - {/* TODO: Add F2F details data */} - {/*
-
-

{viewType}

-
- -
- { - viewType === 'CHALLENGES DETAILS' && ( -
- ) - - } -
-
*/} -
- - ) -} - -export default F2FDetailsModal diff --git a/src/apps/profiles/src/components/F2FDetailsModal/index.ts b/src/apps/profiles/src/components/F2FDetailsModal/index.ts deleted file mode 100644 index 233d449a7..000000000 --- a/src/apps/profiles/src/components/F2FDetailsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as F2FDetailsModal } from './F2FDetailsModal' diff --git a/src/apps/profiles/src/components/GenericSubtrackDetailsModal/GenericSubtrackDetailsModal.module.scss b/src/apps/profiles/src/components/GenericSubtrackDetailsModal/GenericSubtrackDetailsModal.module.scss deleted file mode 100644 index 47e811dd7..000000000 --- a/src/apps/profiles/src/components/GenericSubtrackDetailsModal/GenericSubtrackDetailsModal.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - display: flex; - flex-direction: column; - - .content { - .contentHeader { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid $black-10; - margin: $sp-4 0; - padding-bottom: $sp-2; - - .contentHeaderActions { - button { - margin-right: $sp-2; - - &:last-child { - margin-right: 0; - } - } - } - } - - .contentBody { - height: 385px; - overflow: auto; - - @include ltelg { - height: auto; - } - } - } -} \ No newline at end of file diff --git a/src/apps/profiles/src/components/GenericSubtrackDetailsModal/GenericSubtrackDetailsModal.tsx b/src/apps/profiles/src/components/GenericSubtrackDetailsModal/GenericSubtrackDetailsModal.tsx deleted file mode 100644 index 327f16228..000000000 --- a/src/apps/profiles/src/components/GenericSubtrackDetailsModal/GenericSubtrackDetailsModal.tsx +++ /dev/null @@ -1,148 +0,0 @@ -/* eslint-disable complexity */ -import { Dispatch, FC, SetStateAction, useState } from 'react' -import { get } from 'lodash' -import Highcharts from 'highcharts' -import HighchartsReact from 'highcharts-react-official' - -import { BaseModal } from '~/libs/ui' -import { - getRatingColor, - MemberStats, - UserProfile, - UserStatsDistributionResponse, - UserStatsHistory, - useStatsDistribution, - useStatsHistory, -} from '~/libs/core' - -import { numberToFixed } from '../../lib' -import { useRatingDistroOptions, useRatingHistoryOptions } from '../../hooks' - -import styles from './GenericSubtrackDetailsModal.module.scss' - -type GenericViewTypes = 'CHALLENGES DETAILS' | 'STATISTICS' - -interface GenericSubtrackDetailsModalProps { - onClose: () => void - genericStats: MemberStats | undefined - chartTitle: string | undefined - profile: UserProfile | undefined - subTrack: string - title: string - track: string -} - -const GenericSubtrackDetailsModal: FC = (props: GenericSubtrackDetailsModalProps) => { - // TODO: Enable this when we have challenges details data - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [viewType]: [GenericViewTypes, Dispatch>] - = useState('STATISTICS') - - const statsHistory: UserStatsHistory | undefined = useStatsHistory(props.profile?.handle) - - const ratingHistoryOptions: Highcharts.Options | undefined - = useRatingHistoryOptions( - get(statsHistory, props.track)?.subTracks?.find( - (subTrack: any) => subTrack.name === props.subTrack, - )?.history, - props.chartTitle || '', - ) - - const memberStatsDist: UserStatsDistributionResponse | undefined = useStatsDistribution({ - filter: `track=${props.track}&subTrack=${props.subTrack}`, - }) - - const ratingDistributionOptions: Highcharts.Options | undefined - = useRatingDistroOptions(memberStatsDist?.distribution || {}, props.genericStats?.rank?.rating) - - return ( - - -
-
-
- - {props.genericStats?.rank?.rating || 0} - - Rating -
-
- {props.genericStats?.wins} - Wins -
-
- {props.genericStats?.challenges} - Challenges -
-
- - {numberToFixed(props.genericStats?.rank?.overallPercentile || 0)} - % - - Percentile -
-
- - {numberToFixed(props.genericStats?.screeningSuccessRate || 0)} - % - - Screening Success Rate -
- {/*
- - {numberToFixed(props.genericStats?.avgPlacement || 0)} - - Average Placement -
*/} -
-
-
-

{viewType}

-
- -
- { - viewType === 'CHALLENGES DETAILS' && ( -
- ) - - } - { - viewType === 'STATISTICS' && ( -
- { - ratingHistoryOptions && ( - - ) - } - { - ratingDistributionOptions && ( - - ) - } -
- ) - - } -
-
-
- - ) -} - -export default GenericSubtrackDetailsModal diff --git a/src/apps/profiles/src/components/GenericSubtrackDetailsModal/index.ts b/src/apps/profiles/src/components/GenericSubtrackDetailsModal/index.ts deleted file mode 100644 index 20adec9a9..000000000 --- a/src/apps/profiles/src/components/GenericSubtrackDetailsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as GenericSubtrackDetailsModal } from './GenericSubtrackDetailsModal' diff --git a/src/apps/profiles/src/components/LogoDesignDetailsModal/LogoDesignDetailsModal.module.scss b/src/apps/profiles/src/components/LogoDesignDetailsModal/LogoDesignDetailsModal.module.scss deleted file mode 100644 index 47e811dd7..000000000 --- a/src/apps/profiles/src/components/LogoDesignDetailsModal/LogoDesignDetailsModal.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - display: flex; - flex-direction: column; - - .content { - .contentHeader { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid $black-10; - margin: $sp-4 0; - padding-bottom: $sp-2; - - .contentHeaderActions { - button { - margin-right: $sp-2; - - &:last-child { - margin-right: 0; - } - } - } - } - - .contentBody { - height: 385px; - overflow: auto; - - @include ltelg { - height: auto; - } - } - } -} \ No newline at end of file diff --git a/src/apps/profiles/src/components/LogoDesignDetailsModal/LogoDesignDetailsModal.tsx b/src/apps/profiles/src/components/LogoDesignDetailsModal/LogoDesignDetailsModal.tsx deleted file mode 100644 index 59b09e1df..000000000 --- a/src/apps/profiles/src/components/LogoDesignDetailsModal/LogoDesignDetailsModal.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* eslint-disable complexity */ -import { Dispatch, FC, SetStateAction, useState } from 'react' - -import { BaseModal } from '~/libs/ui' -import { - MemberStats, -} from '~/libs/core' - -import { numberToFixed } from '../../lib' - -import styles from './LogoDesignDetailsModal.module.scss' - -type WebDesignViewTypes = 'CHALLENGES DETAILS' - -interface LogoDesignDetailsModalProps { - onClose: () => void - logoDesignStats: MemberStats | undefined -} - -const LogoDesignDetailsModal: FC = (props: LogoDesignDetailsModalProps) => { - // TODO: Enable this when we have challenges details data - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [viewType]: [WebDesignViewTypes, Dispatch>] - = useState('CHALLENGES DETAILS') - - return ( - - -
-
-
- {props.logoDesignStats?.wins} - Wins -
-
- {props.logoDesignStats?.challenges} - Challenges -
-
- - {numberToFixed(props.logoDesignStats?.rank?.overallPercentile || 0)} - % - - Percentile -
-
- - {numberToFixed(props.logoDesignStats?.screeningSuccessRate || 0)} - % - - Screening Success Rate -
-
- - {numberToFixed(props.logoDesignStats?.avgPlacement || 0)} - - Average Placement -
-
- {/* TODO: Add Logo Design details data */} - {/*
-
-

{viewType}

-
- -
- { - viewType === 'CHALLENGES DETAILS' && ( -
- ) - - } -
-
*/} -
- - ) -} - -export default LogoDesignDetailsModal diff --git a/src/apps/profiles/src/components/LogoDesignDetailsModal/index.ts b/src/apps/profiles/src/components/LogoDesignDetailsModal/index.ts deleted file mode 100644 index 5b69fdc97..000000000 --- a/src/apps/profiles/src/components/LogoDesignDetailsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as LogoDesignDetailsModal } from './LogoDesignDetailsModal' diff --git a/src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.module.scss b/src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.module.scss deleted file mode 100644 index 47e811dd7..000000000 --- a/src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - display: flex; - flex-direction: column; - - .content { - .contentHeader { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid $black-10; - margin: $sp-4 0; - padding-bottom: $sp-2; - - .contentHeaderActions { - button { - margin-right: $sp-2; - - &:last-child { - margin-right: 0; - } - } - } - } - - .contentBody { - height: 385px; - overflow: auto; - - @include ltelg { - height: auto; - } - } - } -} \ No newline at end of file diff --git a/src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.tsx b/src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.tsx deleted file mode 100644 index 6aefb4c3a..000000000 --- a/src/apps/profiles/src/components/MMDetailsModal/MMDetailsModal.tsx +++ /dev/null @@ -1,150 +0,0 @@ -/* eslint-disable complexity */ -import { Dispatch, FC, SetStateAction, useState } from 'react' -import Highcharts from 'highcharts' -import HighchartsReact from 'highcharts-react-official' - -import { BaseModal, LoadingSpinner } from '~/libs/ui' -import { - getRatingColor, - MemberStats, - UserProfile, - UserStatsDistributionResponse, - UserStatsHistory, - useStatsDistribution, - useStatsHistory, -} from '~/libs/core' - -import { useRatingDistroOptions, useRatingHistoryOptions } from '../../hooks' - -import styles from './MMDetailsModal.module.scss' - -type SRMViewTypes = 'STATISTICS' | 'MATCH DETAILS' - -interface MMDetailsModalProps { - onClose: () => void - MMStats: MemberStats | undefined - profile: UserProfile | undefined -} - -const MMDetailsModal: FC = (props: MMDetailsModalProps) => { - const [viewType, setviewType]: [SRMViewTypes, Dispatch>] - = useState('STATISTICS') - - const statsHistory: UserStatsHistory | undefined = useStatsHistory(props.profile?.handle) - - const ratingHistoryOptions: Highcharts.Options | undefined - = useRatingHistoryOptions( - statsHistory?.DATA_SCIENCE?.MARATHON_MATCH?.history, - 'Marathon Match Rating', - ) - - const memberStatsDist: UserStatsDistributionResponse | undefined = useStatsDistribution({ - filter: 'track=DATA_SCIENCE&subTrack=MARATHON_MATCH', - }) - - const ratingDistributionOptions: Highcharts.Options | undefined - = useRatingDistroOptions(memberStatsDist?.distribution || {}, props.MMStats?.rank.rating) - - // TODO: Enable this when we have challenges details data - // eslint-disable-next-line @typescript-eslint/no-unused-vars - function toggleViewType(newViewType: SRMViewTypes): void { - setviewType(newViewType) - } - - return ( - - - - {!!statsHistory && !!memberStatsDist && ( -
-
-
- - {props.MMStats?.rank.rating} - - Rating -
-
- {props.MMStats?.rank.rank} - Rank -
-
- - {props.MMStats?.rank.percentile} - % - - Percentile -
-
- {props.MMStats?.wins} - Wins -
-
- -
-
-

{viewType}

- {/* TODO: Enable this when we have challenges details data */} - {/*
- -
*/} -
- -
- { - viewType === 'STATISTICS' && ( -
- { - ratingHistoryOptions && ( - - ) - } - { - ratingDistributionOptions && ( - - ) - } -
- ) - - } - { - viewType === 'MATCH DETAILS' && ( -
- ) - - } -
-
-
- )} - - ) -} - -export default MMDetailsModal diff --git a/src/apps/profiles/src/components/MMDetailsModal/index.ts b/src/apps/profiles/src/components/MMDetailsModal/index.ts deleted file mode 100644 index b59460e27..000000000 --- a/src/apps/profiles/src/components/MMDetailsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as MMDetailsModal } from './MMDetailsModal' diff --git a/src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.module.scss b/src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.module.scss deleted file mode 100644 index a97b5983b..000000000 --- a/src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.module.scss +++ /dev/null @@ -1,42 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - display: flex; - flex-direction: column; - - .content { - .contentHeader { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid $black-10; - margin: $sp-4 0; - padding-bottom: $sp-2; - - .contentHeaderActions { - button { - margin-right: $sp-2; - - &:last-child { - margin-right: 0; - } - } - } - } - - .contentBody { - height: 385px; - overflow: auto; - - @include ltelg { - height: auto; - } - - .details { - display: flex; - flex-direction: column; - margin-top: $sp-8; - } - } - } -} \ No newline at end of file diff --git a/src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.tsx b/src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.tsx deleted file mode 100644 index 546039b9c..000000000 --- a/src/apps/profiles/src/components/SRMDetailsModal/SRMDetailsModal.tsx +++ /dev/null @@ -1,194 +0,0 @@ -/* eslint-disable complexity */ -import { Dispatch, FC, SetStateAction, useState } from 'react' -import { bind } from 'lodash' -import AnnotationsModule from 'highcharts/modules/annotations' -import Highcharts from 'highcharts' -import HighchartsReact from 'highcharts-react-official' - -import { BaseModal, Button, LoadingSpinner } from '~/libs/ui' -import { - getRatingColor, - SRMStats, - UserProfile, - UserStatsDistributionResponse, - UserStatsHistory, - useStatsDistribution, - useStatsHistory, -} from '~/libs/core' - -import { ChallengesGrid } from '../ChallengesGrid' -import { DivisionGrid } from '../DivisionGrid' -import { useRatingDistroOptions, useRatingHistoryOptions } from '../../hooks' - -import styles from './SRMDetailsModal.module.scss' - -type SRMViewTypes = 'STATISTICS' | 'SRM DETAILS' | 'PAST SRM' - -interface SRMDetailsModalProps { - onClose: () => void - SRMStats: SRMStats | undefined - profile: UserProfile | undefined -} - -AnnotationsModule(Highcharts) - -const SRMDetailsModal: FC = (props: SRMDetailsModalProps) => { - const [viewType, setviewType]: [SRMViewTypes, Dispatch>] - = useState('STATISTICS') - - const statsHistory: UserStatsHistory | undefined = useStatsHistory(props.profile?.handle) - - const ratingHistoryOptions: Highcharts.Options | undefined - = useRatingHistoryOptions( - statsHistory?.DATA_SCIENCE?.SRM?.history, - 'SRM Rating', - ) - - const memberStatsDist: UserStatsDistributionResponse | undefined = useStatsDistribution({ - filter: 'track=DATA_SCIENCE&subTrack=SRM', - }) - - const ratingDistributionOptions: Highcharts.Options | undefined - = useRatingDistroOptions(memberStatsDist?.distribution || {}, props.SRMStats?.rank.rating) - - function toggleViewType(newViewType: SRMViewTypes): void { - setviewType(newViewType) - } - - return ( - - - - {!!statsHistory && !!memberStatsDist && ( -
-
-
- - {props.SRMStats?.rank.rating} - - Rating -
-
- {props.SRMStats?.rank.rank} - Rank -
-
- - {props.SRMStats?.rank.percentile} - % - - Percentile -
-
- - {props.SRMStats?.rank.competitions} - - Competitions -
-
- {props.SRMStats?.rank.volatility} - Volatility -
-
- -
-
-

{viewType}

-
- - {/* TODO: Enable this when we have challenges details data */} - {/* */} -
-
- -
- { - viewType === 'STATISTICS' && ( -
- { - ratingHistoryOptions && ( - - ) - } - { - ratingDistributionOptions && ( - - ) - } -
- ) - - } - { - viewType === 'SRM DETAILS' && ( -
- { - props.SRMStats?.division1 && ( - - ) - } - { - props.SRMStats?.division2 && ( - - ) - } - { - props.SRMStats?.challengeDetails && ( - - ) - } -
- ) - - } - { - viewType === 'PAST SRM' && ( -
- ) - - } -
-
-
- )} - - ) -} - -export default SRMDetailsModal diff --git a/src/apps/profiles/src/components/SRMDetailsModal/index.ts b/src/apps/profiles/src/components/SRMDetailsModal/index.ts deleted file mode 100644 index 894107b63..000000000 --- a/src/apps/profiles/src/components/SRMDetailsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as SRMDetailsModal } from './SRMDetailsModal' diff --git a/src/apps/profiles/src/components/TestScenariosDetailsModal/TestScenariosDetailsModal.module.scss b/src/apps/profiles/src/components/TestScenariosDetailsModal/TestScenariosDetailsModal.module.scss deleted file mode 100644 index 47e811dd7..000000000 --- a/src/apps/profiles/src/components/TestScenariosDetailsModal/TestScenariosDetailsModal.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - display: flex; - flex-direction: column; - - .content { - .contentHeader { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid $black-10; - margin: $sp-4 0; - padding-bottom: $sp-2; - - .contentHeaderActions { - button { - margin-right: $sp-2; - - &:last-child { - margin-right: 0; - } - } - } - } - - .contentBody { - height: 385px; - overflow: auto; - - @include ltelg { - height: auto; - } - } - } -} \ No newline at end of file diff --git a/src/apps/profiles/src/components/TestScenariosDetailsModal/TestScenariosDetailsModal.tsx b/src/apps/profiles/src/components/TestScenariosDetailsModal/TestScenariosDetailsModal.tsx deleted file mode 100644 index 83e1219d9..000000000 --- a/src/apps/profiles/src/components/TestScenariosDetailsModal/TestScenariosDetailsModal.tsx +++ /dev/null @@ -1,155 +0,0 @@ -/* eslint-disable complexity */ -import { Dispatch, FC, SetStateAction, useState } from 'react' -import Highcharts from 'highcharts' -import HighchartsReact from 'highcharts-react-official' - -import { BaseModal, LoadingSpinner } from '~/libs/ui' -import { - getRatingColor, - MemberStats, - UserProfile, - UserStatsDistributionResponse, - UserStatsHistory, - useStatsDistribution, - useStatsHistory, -} from '~/libs/core' - -import { numberToFixed } from '../../lib' -import { useRatingDistroOptions, useRatingHistoryOptions } from '../../hooks' - -import styles from './TestScenariosDetailsModal.module.scss' - -type TestScenViewTypes = 'STATISTICS' | 'CHALLENGES DETAILS' - -interface TestScenariosDetailsModalProps { - onClose: () => void - testScenStats: MemberStats | undefined - profile: UserProfile | undefined -} - -const TestScenariosDetailsModal: FC = (props: TestScenariosDetailsModalProps) => { - const [viewType, setviewType]: [TestScenViewTypes, Dispatch>] - = useState('STATISTICS') - - const statsHistory: UserStatsHistory | undefined = useStatsHistory(props.profile?.handle) - - const ratingHistoryOptions: Highcharts.Options | undefined - = useRatingHistoryOptions( - statsHistory?.DEVELOP?.subTracks?.find(subTrack => subTrack.name === 'TEST_SCENARIOS')?.history, - 'Test Scenarios Rating', - ) - - const memberStatsDist: UserStatsDistributionResponse | undefined = useStatsDistribution({ - filter: 'track=DEVELOP&subTrack=TEST_SCENARIOS', - }) - - const ratingDistributionOptions: Highcharts.Options | undefined - = useRatingDistroOptions(memberStatsDist?.distribution || {}, props.testScenStats?.rank.rating) - - // TODO: Enable this when we have challenges details data - // eslint-disable-next-line @typescript-eslint/no-unused-vars - function toggleViewType(newViewType: TestScenViewTypes): void { - setviewType(newViewType) - } - - return ( - - - - {!!statsHistory && !!memberStatsDist && ( -
-
-
- - {props.testScenStats?.rank.rating} - - Rating -
-
- {props.testScenStats?.rank.overallRank} - Rank -
-
- - {numberToFixed(props.testScenStats?.rank.overallPercentile || 0)} - % - - Percentile -
-
- {props.testScenStats?.wins} - Wins -
-
- {props.testScenStats?.challenges} - Challenges -
-
- -
-
-

{viewType}

- {/* TODO: Enable this when we have challenges details data */} - {/*
- -
*/} -
- -
- { - viewType === 'STATISTICS' && ( -
- { - ratingHistoryOptions && ( - - ) - } - { - ratingDistributionOptions && ( - - ) - } -
- ) - - } - { - viewType === 'CHALLENGES DETAILS' && ( -
- ) - - } -
-
-
- )} - - ) -} - -export default TestScenariosDetailsModal diff --git a/src/apps/profiles/src/components/TestScenariosDetailsModal/index.ts b/src/apps/profiles/src/components/TestScenariosDetailsModal/index.ts deleted file mode 100644 index a862bce64..000000000 --- a/src/apps/profiles/src/components/TestScenariosDetailsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as TestScenariosDetailsModal } from './TestScenariosDetailsModal' diff --git a/src/apps/profiles/src/components/UIPrototypeDetailsModal/UIPrototypeDetailsModal.module.scss b/src/apps/profiles/src/components/UIPrototypeDetailsModal/UIPrototypeDetailsModal.module.scss deleted file mode 100644 index 47e811dd7..000000000 --- a/src/apps/profiles/src/components/UIPrototypeDetailsModal/UIPrototypeDetailsModal.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - display: flex; - flex-direction: column; - - .content { - .contentHeader { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid $black-10; - margin: $sp-4 0; - padding-bottom: $sp-2; - - .contentHeaderActions { - button { - margin-right: $sp-2; - - &:last-child { - margin-right: 0; - } - } - } - } - - .contentBody { - height: 385px; - overflow: auto; - - @include ltelg { - height: auto; - } - } - } -} \ No newline at end of file diff --git a/src/apps/profiles/src/components/UIPrototypeDetailsModal/UIPrototypeDetailsModal.tsx b/src/apps/profiles/src/components/UIPrototypeDetailsModal/UIPrototypeDetailsModal.tsx deleted file mode 100644 index db20391f2..000000000 --- a/src/apps/profiles/src/components/UIPrototypeDetailsModal/UIPrototypeDetailsModal.tsx +++ /dev/null @@ -1,155 +0,0 @@ -/* eslint-disable complexity */ -import { Dispatch, FC, SetStateAction, useState } from 'react' -import Highcharts from 'highcharts' -import HighchartsReact from 'highcharts-react-official' - -import { BaseModal, LoadingSpinner } from '~/libs/ui' -import { - getRatingColor, - MemberStats, - UserProfile, - UserStatsDistributionResponse, - UserStatsHistory, - useStatsDistribution, - useStatsHistory, -} from '~/libs/core' - -import { numberToFixed } from '../../lib' -import { useRatingDistroOptions, useRatingHistoryOptions } from '../../hooks' - -import styles from './UIPrototypeDetailsModal.module.scss' - -type UIPrototypeViewTypes = 'STATISTICS' | 'CHALLENGES DETAILS' - -interface UIPrototypeDetailsModalProps { - onClose: () => void - uiPrototypeStats: MemberStats | undefined - profile: UserProfile | undefined -} - -const UIPrototypeDetailsModal: FC = (props: UIPrototypeDetailsModalProps) => { - const [viewType, setviewType]: [UIPrototypeViewTypes, Dispatch>] - = useState('STATISTICS') - - const statsHistory: UserStatsHistory | undefined = useStatsHistory(props.profile?.handle) - - const ratingHistoryOptions: Highcharts.Options | undefined - = useRatingHistoryOptions( - statsHistory?.DEVELOP?.subTracks?.find(subTrack => subTrack.name === 'UI_PROTOTYPE_COMPETITION')?.history, - 'UI Prototype Rating', - ) - - const memberStatsDist: UserStatsDistributionResponse | undefined = useStatsDistribution({ - filter: 'track=DEVELOP&subTrack=UI_PROTOTYPE_COMPETITION', - }) - - const ratingDistributionOptions: Highcharts.Options | undefined - = useRatingDistroOptions(memberStatsDist?.distribution || {}, props.uiPrototypeStats?.rank.rating) - - // TODO: Enable this when we have challenges details data - // eslint-disable-next-line @typescript-eslint/no-unused-vars - function toggleViewType(newViewType: UIPrototypeViewTypes): void { - setviewType(newViewType) - } - - return ( - - - - {!!statsHistory && !!memberStatsDist && ( -
-
-
- - {props.uiPrototypeStats?.rank.rating} - - Rating -
-
- {props.uiPrototypeStats?.rank.overallRank} - Rank -
-
- - {numberToFixed(props.uiPrototypeStats?.rank.overallPercentile || 0)} - % - - Percentile -
-
- {props.uiPrototypeStats?.wins} - Wins -
-
- {props.uiPrototypeStats?.challenges} - Challenges -
-
- -
-
-

{viewType}

- {/* TODO: Add UI Prototype details data */} - {/*
- -
*/} -
- -
- { - viewType === 'STATISTICS' && ( -
- { - ratingHistoryOptions && ( - - ) - } - { - ratingDistributionOptions && ( - - ) - } -
- ) - - } - { - viewType === 'CHALLENGES DETAILS' && ( -
- ) - - } -
-
-
- )} - - ) -} - -export default UIPrototypeDetailsModal diff --git a/src/apps/profiles/src/components/UIPrototypeDetailsModal/index.ts b/src/apps/profiles/src/components/UIPrototypeDetailsModal/index.ts deleted file mode 100644 index f81eb8b46..000000000 --- a/src/apps/profiles/src/components/UIPrototypeDetailsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as UIPrototypeDetailsModal } from './UIPrototypeDetailsModal' diff --git a/src/apps/profiles/src/components/WebDesignDetailsModal/WebDesignDetailsModal.module.scss b/src/apps/profiles/src/components/WebDesignDetailsModal/WebDesignDetailsModal.module.scss deleted file mode 100644 index 47e811dd7..000000000 --- a/src/apps/profiles/src/components/WebDesignDetailsModal/WebDesignDetailsModal.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - display: flex; - flex-direction: column; - - .content { - .contentHeader { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid $black-10; - margin: $sp-4 0; - padding-bottom: $sp-2; - - .contentHeaderActions { - button { - margin-right: $sp-2; - - &:last-child { - margin-right: 0; - } - } - } - } - - .contentBody { - height: 385px; - overflow: auto; - - @include ltelg { - height: auto; - } - } - } -} \ No newline at end of file diff --git a/src/apps/profiles/src/components/WebDesignDetailsModal/WebDesignDetailsModal.tsx b/src/apps/profiles/src/components/WebDesignDetailsModal/WebDesignDetailsModal.tsx deleted file mode 100644 index f997b32b2..000000000 --- a/src/apps/profiles/src/components/WebDesignDetailsModal/WebDesignDetailsModal.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* eslint-disable complexity */ -import { Dispatch, FC, SetStateAction, useState } from 'react' - -import { BaseModal } from '~/libs/ui' -import { - MemberStats, -} from '~/libs/core' - -import { numberToFixed } from '../../lib' - -import styles from './WebDesignDetailsModal.module.scss' - -type WebDesignViewTypes = 'CHALLENGES DETAILS' - -interface WebDesignDetailsModalProps { - onClose: () => void - webDesignStats: MemberStats | undefined -} - -const WebDesignDetailsModal: FC = (props: WebDesignDetailsModalProps) => { - // TODO: Enable this when we have challenges details data - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [viewType]: [WebDesignViewTypes, Dispatch>] - = useState('CHALLENGES DETAILS') - - return ( - - -
-
-
- {props.webDesignStats?.wins} - Wins -
-
- {props.webDesignStats?.challenges} - Challenges -
-
- - {numberToFixed(props.webDesignStats?.rank?.overallPercentile || 0)} - % - - Percentile -
-
- - {numberToFixed(props.webDesignStats?.screeningSuccessRate || 0)} - % - - Screening Success Rate -
-
- - {numberToFixed(props.webDesignStats?.avgPlacement || 0)} - - Average Placement -
-
- - {/* TODO: Enable this when we have challenges details data */} - {/*
-
-

{viewType}

-
- -
- { - viewType === 'CHALLENGES DETAILS' && ( -
- ) - - } -
-
*/} -
- - ) -} - -export default WebDesignDetailsModal diff --git a/src/apps/profiles/src/components/WebDesignDetailsModal/index.ts b/src/apps/profiles/src/components/WebDesignDetailsModal/index.ts deleted file mode 100644 index 084fad4d3..000000000 --- a/src/apps/profiles/src/components/WebDesignDetailsModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as WebDesignDetailsModal } from './WebDesignDetailsModal' diff --git a/src/apps/profiles/src/components/index.ts b/src/apps/profiles/src/components/index.ts index 3e3acf994..8bb7e5e34 100644 --- a/src/apps/profiles/src/components/index.ts +++ b/src/apps/profiles/src/components/index.ts @@ -1,20 +1,6 @@ export * from './MemberBadgeModal' -export * from './CopilotDetailsModal' -export * from './SRMDetailsModal' -export * from './DivisionGrid' -export * from './ChallengesGrid' -export * from './MMDetailsModal' -export * from './TestScenariosDetailsModal' -export * from './BugHuntDetailsModal' -export * from './ContentCreationDetailsModal' -export * from './UIPrototypeDetailsModal' -export * from './CodeDetailsModal' -export * from './F2FDetailsModal' -export * from './AssemblyDetailsModal' -export * from './LogoDesignDetailsModal' -export * from './WebDesignDetailsModal' -export * from './DesignF2FDetailsModal' +export * from './tc-achievements/SRMView/DivisionGrid' +export * from './tc-achievements/SRMView/ChallengesGrid' export * from './EditMemberPropertyBtn' export * from './AddButton' export * from './EmptySection' -export * from './GenericSubtrackDetailsModal' diff --git a/src/apps/profiles/src/components/ChallengesGrid/ChallengesGrid.module.scss b/src/apps/profiles/src/components/tc-achievements/SRMView/ChallengesGrid/ChallengesGrid.module.scss similarity index 100% rename from src/apps/profiles/src/components/ChallengesGrid/ChallengesGrid.module.scss rename to src/apps/profiles/src/components/tc-achievements/SRMView/ChallengesGrid/ChallengesGrid.module.scss diff --git a/src/apps/profiles/src/components/ChallengesGrid/ChallengesGrid.tsx b/src/apps/profiles/src/components/tc-achievements/SRMView/ChallengesGrid/ChallengesGrid.tsx similarity index 100% rename from src/apps/profiles/src/components/ChallengesGrid/ChallengesGrid.tsx rename to src/apps/profiles/src/components/tc-achievements/SRMView/ChallengesGrid/ChallengesGrid.tsx diff --git a/src/apps/profiles/src/components/ChallengesGrid/index.ts b/src/apps/profiles/src/components/tc-achievements/SRMView/ChallengesGrid/index.ts similarity index 100% rename from src/apps/profiles/src/components/ChallengesGrid/index.ts rename to src/apps/profiles/src/components/tc-achievements/SRMView/ChallengesGrid/index.ts diff --git a/src/apps/profiles/src/components/DivisionGrid/DivisionGrid.module.scss b/src/apps/profiles/src/components/tc-achievements/SRMView/DivisionGrid/DivisionGrid.module.scss similarity index 100% rename from src/apps/profiles/src/components/DivisionGrid/DivisionGrid.module.scss rename to src/apps/profiles/src/components/tc-achievements/SRMView/DivisionGrid/DivisionGrid.module.scss diff --git a/src/apps/profiles/src/components/DivisionGrid/DivisionGrid.tsx b/src/apps/profiles/src/components/tc-achievements/SRMView/DivisionGrid/DivisionGrid.tsx similarity index 100% rename from src/apps/profiles/src/components/DivisionGrid/DivisionGrid.tsx rename to src/apps/profiles/src/components/tc-achievements/SRMView/DivisionGrid/DivisionGrid.tsx diff --git a/src/apps/profiles/src/components/DivisionGrid/index.ts b/src/apps/profiles/src/components/tc-achievements/SRMView/DivisionGrid/index.ts similarity index 100% rename from src/apps/profiles/src/components/DivisionGrid/index.ts rename to src/apps/profiles/src/components/tc-achievements/SRMView/DivisionGrid/index.ts diff --git a/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx b/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx index f863927b3..b86cfa34b 100644 --- a/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx +++ b/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx @@ -12,10 +12,10 @@ import { } from '~/libs/core' import { DetailedTrackView } from '../DetailedTrackView' -import { DivisionGrid } from '../../DivisionGrid' -import { ChallengesGrid } from '../../ChallengesGrid' import { ViewMode } from '../DetailedTrackView/DetailedTrackView' +import { DivisionGrid } from './DivisionGrid' +import { ChallengesGrid } from './ChallengesGrid' import styles from './SRMView.module.scss' interface SRMViewProps { diff --git a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx index 9418ef6b2..0507622a0 100644 --- a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx +++ b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx @@ -3,6 +3,9 @@ import { filter, find, orderBy } from 'lodash' import { MemberStats, SRMStats, useMemberStats, UserStats } from '~/libs/core' +/** + * The structure of a track for a member. + */ export interface MemberStatsTrack { challenges?: number, isActive: boolean, @@ -13,13 +16,22 @@ export interface MemberStatsTrack { wins: number, } +/** + * Helper function to build aggregated data for a track. + * + * @param {string} trackName - The name of the track. + * @param {MemberStats[]} subTracks - List of subtracks within the main track. + * @returns {MemberStatsTrack} - Aggregated data for the track. + */ const buildTrackData = (trackName: string, subTracks: MemberStats[]): MemberStatsTrack => { + // Calculate total wins, challenges, and submissions for the track const totalWins = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.wins || 0)), 0) const challengesCount = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.challenges || 0)), 0) const submissionsCount = subTracks.reduce((sum, subTrack) => ( sum + (subTrack?.submissions?.submissions || 0) ), 0) + // Return aggregated track data return { challenges: challengesCount, isActive: challengesCount > 0, @@ -30,10 +42,18 @@ const buildTrackData = (trackName: string, subTracks: MemberStats[]): MemberStat } } +/** + * Custom hook to fetch active tracks for a user, sorted by wins & submissions. + * + * @param {string} userHandle - The user's handle. + * @returns {MemberStatsTrack[]} - List of active tracks for the user. + */ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => { const memberStats: UserStats | undefined = useMemberStats(userHandle) + // Create mappings for data science subtracks const dataScienceSubTracks: {[key: string]: MemberStats | SRMStats} = useMemo(() => ({ + // Map MARATHON_MATCH subtrack MARATHON_MATCH: (memberStats?.DATA_SCIENCE?.MARATHON_MATCH && ({ ...memberStats.DATA_SCIENCE.MARATHON_MATCH, name: 'MARATHON_MATCH', @@ -41,6 +61,7 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => path: 'DATA_SCIENCE', })) as MemberStats, + // Map SRM subtrack SRM: (memberStats?.DATA_SCIENCE?.SRM && ({ ...memberStats.DATA_SCIENCE.SRM, name: 'SRM', @@ -49,7 +70,7 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => })) as SRMStats & {name: string}, }), [memberStats]) - // Create mappings for the subtracks, by the subtrack name, so we can easily access it later on + // Create mappings for design subtracks const designSubTracks: {[key: string]: MemberStats} = useMemo(() => ( memberStats?.DESIGN?.subTracks.reduce((all, subTrack) => { all[subTrack.name] = { @@ -61,6 +82,7 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => }, {} as {[key: string]: MemberStats}) ?? {} ), [memberStats]) + // Create mappings for develop subtracks const developSubTracks: {[key: string]: MemberStats} = useMemo(() => ( memberStats?.DEVELOP?.subTracks.reduce((all, subTrack) => { all[subTrack.name] = { @@ -72,6 +94,10 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => }, {} as {[key: string]: MemberStats}) ?? {} ), [memberStats]) + // Build aggregated stats for Design, Development, Testing, and Competitive Programming tracks + // Each track is constructed using the buildTrackData helper function + // The useMemo hook is used to memoize the results for performance optimization + // Design const designTrackStats: MemberStatsTrack = useMemo(() => ( buildTrackData('Design', [ @@ -113,6 +139,7 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => // Competitive Programming const cpTrackStats: MemberStatsTrack = useMemo(() => { + // Aggregate stats for Competitive Programming track const subTracks = [ dataScienceSubTracks.MARATHON_MATCH, dataScienceSubTracks.SRM, @@ -131,6 +158,7 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => } }, [dataScienceSubTracks, memberStats]) + // Order and filter active tracks based on wins and submissions return orderBy(filter([ cpTrackStats, designTrackStats, @@ -139,18 +167,46 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => ], { isActive: true }), ['wins', 'submissions'], 'desc') } -export const useFetchTrackData = (userHandle: string, track: string | undefined): any => { +/** + * Custom hook to fetch data for a specific track. + * + * @param {string} userHandle - The user's handle. + * @param {string | undefined} track - The name of the track to fetch. + * @returns {MemberStatsTrack | undefined} - Data for the specified track or undefined if not found. + */ +export const useFetchTrackData = (userHandle: string, track: string | undefined): MemberStatsTrack | undefined => { const activeTracks = useFetchActiveTracks(userHandle) + // Find and return the specified track from the active tracks return find(activeTracks, { name: track }) } +/** + * Interface defining the shape of subtrack data. + */ +interface SubTrackData extends Partial { + trackData: MemberStatsTrack, +} + +/** + * Custom hook to fetch data for a specific subtrack within a track. + * + * @param {string} userHandle - The user's handle. + * @param {string | undefined} track - The name of the track containing the subtrack. + * @param {string | undefined} subTrack - The name of the subtrack to fetch. + * @returns {SubTrackData | undefined} - Data for the specified subtrack or undefined if not found. + */ export const useFetchSubTrackData = ( userHandle: string, track: string | undefined, subTrack: string | undefined, -): any => { +): SubTrackData | undefined => { const activeTracks = useFetchActiveTracks(userHandle) const trackData = find(activeTracks, { name: track }) + + if (!trackData) { + return undefined + } + const subTrackData = find(trackData?.subTracks, { name: subTrack }) return { diff --git a/src/apps/profiles/src/hooks/useRatingDistroOptions.tsx b/src/apps/profiles/src/hooks/useRatingDistroOptions.tsx index e33d1dfbb..9244e9de1 100644 --- a/src/apps/profiles/src/hooks/useRatingDistroOptions.tsx +++ b/src/apps/profiles/src/hooks/useRatingDistroOptions.tsx @@ -54,59 +54,77 @@ const getNonZeroRanges = (ranges: TrackRange[]): TrackRange[] => { return ranges.slice(st, end + 1) } +// Define the default configuration for the Highcharts rating distribution chart export const RATING_DISTRO_CHART_CONFIG: Highcharts.Options = { - chart: { - type: 'column', - }, - credits: { - enabled: false, - }, - legend: { - enabled: false, - }, + // Set chart type to column + chart: { type: 'column' }, + // Disable credits (attribution) in the chart + credits: { enabled: false }, + // Disable legend (chart legend) + legend: { enabled: false }, + // Configure plot options for the column chart plotOptions: { column: { + // Disable data labels for individual data points dataLabels: { enabled: false, }, + // Adjust spacing for groups and individual points in the column chart groupPadding: 0.025, minPointLength: 5, pointPadding: 0, }, }, + // Set chart title title: { text: 'RATING DISTRIBUTION', }, + // Configure tooltip display for data points tooltip: { pointFormat: '{point.y:.0f} Coders', }, - xAxis: { - visible: false, - }, + // Hide the X-axis (horizontal axis) + xAxis: { visible: false }, + // Configure the Y-axis (vertical axis) yAxis: { + // Set a title for the Y-axis title: { text: 'Rating', }, }, } +/** + * Custom hook to generate Highcharts options for a rating distribution chart. + * + * @param {UserStatsDistributionResponse['distribution']} ratingDistro - The rating distribution data. + * @param {number | undefined} memberRating - The rating of the member (if available). + * @returns {Highcharts.Options | undefined} - Highcharts options for the rating distribution + * chart or undefined if data is empty. + */ export function useRatingDistroOptions( ratingDistro: UserStatsDistributionResponse['distribution'], memberRating: number | undefined, ): Highcharts.Options | undefined { + // Memoized Highcharts options to optimize performance const ratingDistributionOptions: Highcharts.Options | undefined = useMemo(() => { + // Deep clone of the default chart configuration const options: Highcharts.Options = cloneDeep(RATING_DISTRO_CHART_CONFIG) + + // Get non-zero rating ranges from the distribution data const ranges = getNonZeroRanges(getRanges(ratingDistro)) + // Return undefined if the rating distribution data is empty if (isEmpty(ratingDistro)) return undefined + // Add annotation for the member's rating if available if (!!memberRating) { - // if member is defined, find the group that the member belongs to - // and add an annotation to the chart + // Find the group that the member belongs to based on rating range const memberRatingGroup: string = ranges.find((d: TrackRange) => ( memberRating >= d.start && memberRating <= d.end ))?.name || '' + // Add annotation to the chart for the member's rating group options.annotations = [{ labels: [{ point: memberRatingGroup, @@ -115,6 +133,7 @@ export function useRatingDistroOptions( }] } + // Configure series for the chart options.series = [{ colorByPoint: true, colors: ranges.map(r => getRatingColor(r.start)), diff --git a/src/apps/profiles/src/hooks/useRatingHistoryOptions.tsx b/src/apps/profiles/src/hooks/useRatingHistoryOptions.tsx index 78150e8ad..6304e435c 100644 --- a/src/apps/profiles/src/hooks/useRatingHistoryOptions.tsx +++ b/src/apps/profiles/src/hooks/useRatingHistoryOptions.tsx @@ -4,41 +4,61 @@ import Highcharts from 'highcharts' import { StatsHistory } from '~/libs/core' +// Define the default configuration for the Highcharts rating history chart export const RATING_CHART_CONFIG: Highcharts.Options = { - credits: { - enabled: false, - }, + // Disable credits (attribution) in the chart + credits: { enabled: false }, + // Set chart title title: { text: 'RATING HISTORY', }, + // Configure tooltip display for data points tooltip: { pointFormat: '{point.x:%Y-%m-%d}: {point.y:.0f}', }, + // Configure the X-axis (horizontal axis) xAxis: { + // Configure labels on the X-axis with a specific date format labels: { format: '{value:%Y-%m-%d}', }, + // Set the type of the X-axis to datetime type: 'datetime', }, + // Configure the Y-axis (vertical axis) yAxis: { + // Set a title for the Y-axis title: { text: 'Rating', }, }, } +/** + * Custom hook to generate Highcharts options for a rating history chart. + * + * @param {Array | undefined} trackHistory - The array of historical stats data. + * @param {string} seriesName - The name of the series for the chart. + * @returns {Highcharts.Options | undefined} - Highcharts options for the rating history chart or + * undefined if data is empty. + */ export function useRatingHistoryOptions( trackHistory: Array | undefined, seriesName: string, ): Highcharts.Options | undefined { + // Memoized Highcharts options to optimize performance const ratingHistoryOptions: Highcharts.Options | undefined = useMemo(() => { + // Deep clone of the default chart configuration const options: Highcharts.Options = cloneDeep(RATING_CHART_CONFIG) + // Return undefined if the track history data is empty if (!trackHistory?.length) return undefined + // Determine the date and rating fields based on the first entry in the track history const dateField: string = get(trackHistory[0], 'date') ? 'date' : 'ratingDate' const ratingField: string = get(trackHistory[0], 'rating') ? 'rating' : 'newRating' + // Configure series for the chart options.series = [{ data: trackHistory.sort((a, b) => get(b, dateField) - get(a, dateField)) .map((challenge: StatsHistory) => ({ From edebad0856e7eda5b1377f6e79fc536f04d59493 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 21 Nov 2023 10:50:17 +0200 Subject: [PATCH 20/37] MP-356 - add default view for tracks & subtracks --- .../tc-achievements/MemberTCAchievements.tsx | 26 ++++++++++--------- .../sub-track-view/SubTrackView.tsx | 9 ++++--- .../tc-achievements/track-view/TrackView.tsx | 5 ++-- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx b/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx index 2796e300c..abec84689 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx +++ b/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx @@ -1,4 +1,4 @@ -import { FC, useMemo } from 'react' +import { FC, useCallback, useMemo } from 'react' import { Outlet, Route, Routes } from 'react-router-dom' import { @@ -37,6 +37,16 @@ const MemberTCAchievements: FC = (props: MemberTCAchi (badge: UserBadge) => /TCO.*Trip Winner/.test(badge.org_badge.badge_name), ).length || 0, [memberBadges]) + const renderDefaultRoute = useCallback(() => ( + + ), [memberStats, props.profile, tcoQualifications, tcoTrips, tcoWins]) + if (!memberStats?.wins && !tcoWins && !tcoQualifications) { return <> } @@ -47,26 +57,18 @@ const MemberTCAchievements: FC = (props: MemberTCAchi - )} + element={renderDefaultRoute()} /> + )} /> + )} /> diff --git a/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx b/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx index 87f03fc05..bbfa5231d 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx +++ b/src/apps/profiles/src/member-profile/tc-achievements/sub-track-view/SubTrackView.tsx @@ -1,20 +1,21 @@ -import { FC } from 'react' +import { FC, ReactElement } from 'react' import { useParams } from 'react-router-dom' +import { isEmpty } from 'lodash' import { UserProfile } from '~/libs/core' -// import { ChallengeHistoryView } from '../../../components/tc-achievements/ChallengeHistoryView' import { useFetchSubTrackData } from '../../../hooks' import { StatsDetailsLayout } from '../../../components/tc-achievements/StatsDetailsLayout' import { DevelopTrackView } from '../../../components/tc-achievements/DevelopTrackView' +import { SRMView } from '../../../components/tc-achievements/SRMView' import { getUserProfileRoute, getUserProfileStatsRoute } from '../../../profiles.routes' import { subTrackLabelToHumanName } from '../../../lib' -import { SRMView } from '../../../components/tc-achievements/SRMView' import styles from './SubTrackView.module.scss' interface SubTrackViewProps { profile: UserProfile + renderDefault: () => ReactElement } const SubTrackView: FC = props => { @@ -22,7 +23,7 @@ const SubTrackView: FC = props => { const { trackData, ...subTrackData }: any = useFetchSubTrackData(props.profile.handle, params.trackType, params.subTrack) - return trackData && subTrackData && ( + return (!trackData || isEmpty(subTrackData)) ? props.renderDefault() : (
ReactElement } const TrackView: FC = props => { const params = useParams() const trackData = useFetchTrackData(props.profile.handle, params.trackType) - return ( + return !trackData ? props.renderDefault() : (
Date: Tue, 21 Nov 2023 16:52:30 +0200 Subject: [PATCH 21/37] MP-356 - mobile responsiveness & update to v5 distribution api --- .../ChallengeHistoryView.module.scss | 19 +++++++++---- .../ChallengeHistoryView.tsx | 14 ++++++---- .../DetailedTrackView.module.scss | 13 +++++++++ .../DevelopTrackView/DevelopTrackView.tsx | 3 +- .../tc-achievements/SRMView/SRMView.tsx | 3 +- .../src/hooks/useFetchActiveTracks.tsx | 2 +- .../track-view/TrackView.module.scss | 17 +++++++---- .../tc-achievements/track-view/TrackView.tsx | 28 ++++++++++--------- .../data-providers/useStatsDistribution.ts | 5 ++-- .../profile-store/profile-endpoint.config.ts | 2 +- 10 files changed, 71 insertions(+), 35 deletions(-) diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.module.scss b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.module.scss index df9dfc7ac..508f64615 100644 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.module.scss +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.module.scss @@ -1,15 +1,24 @@ @import '@libs/ui/styles/includes'; .wrap { - display: flex; - flex-wrap: wrap; - gap: $sp-6; + container-type: inline-size; +} + +.inner { max-height: 85vh; overflow: auto; padding-right: $sp-4; margin-right: -$sp-4; - > * { - width: calc(33% - 14px); + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: $sp-6; + + @container (max-width: 700px) { + grid-template-columns: repeat(2, 1fr); + } + + @container (max-width: 368px) { + grid-template-columns: 1fr; } } diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx index 7bdb73479..fbb15f67e 100644 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx @@ -20,12 +20,14 @@ const ChallengeHistoryView: FC = props => { ) return (
- {trackHistory.map(challenge => ( - - ))} +
+ {trackHistory.map(challenge => ( + + ))} +
) } diff --git a/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.module.scss b/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.module.scss index 47c7f6b64..b84f1ee5e 100644 --- a/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.module.scss +++ b/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.module.scss @@ -1,5 +1,9 @@ @import '@libs/ui/styles/includes'; +.wrap { + container-type: inline-size; +} + .chartTitle { font-size: 18px; line-height: 24px; @@ -14,6 +18,15 @@ .btnsGroup.toRight { margin-left: auto; } + + @container (max-width: 600px) { + flex-direction: column; + .btnsGroup { + &.toRight { + order: -1; + } + } + } } .btnsGroup { diff --git a/src/apps/profiles/src/components/tc-achievements/DevelopTrackView/DevelopTrackView.tsx b/src/apps/profiles/src/components/tc-achievements/DevelopTrackView/DevelopTrackView.tsx index 86cec5eb8..a8e05b778 100644 --- a/src/apps/profiles/src/components/tc-achievements/DevelopTrackView/DevelopTrackView.tsx +++ b/src/apps/profiles/src/components/tc-achievements/DevelopTrackView/DevelopTrackView.tsx @@ -25,7 +25,8 @@ const DevelopTrackView: FC = props => { const trackHistory = get(find(get(statsHistory, `${props.trackData.path}`), { name: trackName }), 'history') const ratingDistribution: UserStatsDistributionResponse | undefined = useStatsDistribution({ - filter: `track=${props.trackData.parentTrack}&subTrack=${trackName}`, + subTrack: trackName, + track: props.trackData.parentTrack, }) const showDetailsViewBtn = useMemo(() => ( diff --git a/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx b/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx index b86cfa34b..770ab0a5c 100644 --- a/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx +++ b/src/apps/profiles/src/components/tc-achievements/SRMView/SRMView.tsx @@ -30,7 +30,8 @@ const SRMView: FC = props => { const trackHistory = get(statsHistory, `${props.trackData.path}.${trackName}.history`) const ratingDistribution: UserStatsDistributionResponse | undefined = useStatsDistribution({ - filter: `track=${props.trackData.parentTrack}&subTrack=${trackName}`, + subTrack: trackName, + track: props.trackData.parentTrack, }) const showDetailsViewBtn = useMemo(() => Boolean( diff --git a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx index 0507622a0..07a951a85 100644 --- a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx +++ b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx @@ -143,7 +143,7 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => const subTracks = [ dataScienceSubTracks.MARATHON_MATCH, dataScienceSubTracks.SRM, - ].filter(Boolean) as MemberStats[] + ].filter(d => d?.challenges > 0) as MemberStats[] return { challenges: memberStats?.DATA_SCIENCE?.challenges ?? 0, diff --git a/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.module.scss b/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.module.scss index bb76b05a5..4183a496f 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.module.scss +++ b/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.module.scss @@ -1,12 +1,19 @@ @import '@libs/ui/styles/includes'; .cardsWrap { - display: flex; - flex-wrap: wrap; + container-type: inline-size; +} + +.cardsInner { + display: grid; + grid-template-columns: repeat(3, 1fr); gap: $sp-6; - > * { - display: block; - width: calc(33% - 14px); + @container (max-width: 720px) { + grid-template-columns: repeat(2, 1fr); + } + + @container (max-width: 368px) { + grid-template-columns: 1fr; } } diff --git a/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx b/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx index 23a9b96f9..a13d75506 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx +++ b/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx @@ -29,20 +29,22 @@ const TrackView: FC = props => { trackData={trackData} >
- {trackData.subTracks.map((subTrack: MemberStats) => ( - - + {trackData.subTracks.map((subTrack: MemberStats) => ( + - - ))} + > + + + ))} +
diff --git a/src/libs/core/lib/profile/data-providers/useStatsDistribution.ts b/src/libs/core/lib/profile/data-providers/useStatsDistribution.ts index 2ec0a6222..36047eaaf 100644 --- a/src/libs/core/lib/profile/data-providers/useStatsDistribution.ts +++ b/src/libs/core/lib/profile/data-providers/useStatsDistribution.ts @@ -16,12 +16,13 @@ export interface UserStatsDistributionResponse { } interface UserStatsDistributionQuery { - filter?: string + subTrack?: string + track?: string } export function useStatsDistribution(query?: UserStatsDistributionQuery): UserStatsDistributionResponse | undefined { const { data }: SWRResponse = useSWR(`${memberStatsDistroURL()}?${qs.stringify(query)}`) - return data ? data.result.content : undefined + return data } diff --git a/src/libs/core/lib/profile/profile-functions/profile-store/profile-endpoint.config.ts b/src/libs/core/lib/profile/profile-functions/profile-store/profile-endpoint.config.ts index 7d3b2da30..a8dc69dea 100644 --- a/src/libs/core/lib/profile/profile-functions/profile-store/profile-endpoint.config.ts +++ b/src/libs/core/lib/profile/profile-functions/profile-store/profile-endpoint.config.ts @@ -26,7 +26,7 @@ export function learnBaseURL(): string { } export function memberStatsDistroURL(): string { - return `${EnvironmentConfig.API.V3}/members/stats/distribution` + return `${EnvironmentConfig.API.V5}/members/stats/distribution` } export function memberModifyURL(): string { From 2bb35d2d7a72823f3f6ef9d03690037e994f5a20 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 21 Nov 2023 16:54:19 +0200 Subject: [PATCH 22/37] MP-356 - deploy to dev --- .circleci/config.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6a8a8d974..63368fb3e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -257,9 +257,7 @@ workflows: branches: only: - dev - - justin_fixes - - talent_search_fixes - - feature/standardized-skills + - MP-356_member-stats-and-history - deployQa: context: org-global From aaa03e2cf2149ab565cf5dba931484f87a90b03f Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 22 Nov 2023 10:30:45 +0200 Subject: [PATCH 23/37] Various QA issues fixing --- .../ChallengeHistoryCard.tsx | 10 +++++-- .../MemberStatsBlock/MemberStatsBlock.tsx | 7 ++--- .../src/hooks/useFetchActiveTracks.tsx | 5 ++-- .../src/hooks/useRatingHistoryOptions.tsx | 26 +++++++++++++++++-- src/config/environments/default.env.ts | 1 + .../environments/global-config.model.ts | 1 + 6 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx index c0b3649e5..87b074575 100644 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx @@ -2,6 +2,7 @@ import { FC } from 'react' import { IconSolid } from '~/libs/ui' import { getRatingColor, StatsHistory } from '~/libs/core' +import { EnvironmentConfig } from '~/config' import styles from './ChallengeHistoryCard.module.scss' @@ -10,7 +11,12 @@ interface ChallengeHistoryCardProps { } const ChallengeHistoryCard: FC = props => ( -
+
@@ -38,7 +44,7 @@ const ChallengeHistoryCard: FC = props => (
-
+
) export default ChallengeHistoryCard diff --git a/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx index 8970f461a..f86ff64d8 100644 --- a/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx +++ b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx @@ -69,9 +69,10 @@ const MemberStatsBlock: FC = props => { ))}

- - Topcoder challenges are open competitions where community - members participate in small units of work to deliver projects. + + Topcoder challenges are competitive events where community members collaborate + on smaller tasks to complete a project, + striving to showcase their skills and outperform others.

diff --git a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx index 07a951a85..5834bbd36 100644 --- a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx +++ b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx @@ -125,6 +125,7 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => developSubTracks.ASSEMBLY_COMPETITION, developSubTracks.UI_PROTOTYPE_COMPETITION, developSubTracks.SPECIFICATION, + developSubTracks.CONCEPTUALIZATION, ].filter(Boolean)) ), [developSubTracks]) @@ -150,8 +151,8 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => isActive: (memberStats?.DATA_SCIENCE?.challenges ?? 0) > 0, name: 'Competitive Programming', ranking: Math.max( - dataScienceSubTracks.MARATHON_MATCH?.rank.percentile ?? 0, - dataScienceSubTracks.SRM?.rank.percentile ?? 0, + dataScienceSubTracks.MARATHON_MATCH?.rank?.percentile ?? 0, + dataScienceSubTracks.SRM?.rank?.percentile ?? 0, ), subTracks, wins: memberStats?.DATA_SCIENCE?.wins ?? 0, diff --git a/src/apps/profiles/src/hooks/useRatingHistoryOptions.tsx b/src/apps/profiles/src/hooks/useRatingHistoryOptions.tsx index 6304e435c..696b11cb1 100644 --- a/src/apps/profiles/src/hooks/useRatingHistoryOptions.tsx +++ b/src/apps/profiles/src/hooks/useRatingHistoryOptions.tsx @@ -2,10 +2,20 @@ import { useMemo } from 'react' import { cloneDeep, get } from 'lodash' import Highcharts from 'highcharts' -import { StatsHistory } from '~/libs/core' +import { getRatingColor, StatsHistory, TC_RATING_COLORS } from '~/libs/core' // Define the default configuration for the Highcharts rating history chart export const RATING_CHART_CONFIG: Highcharts.Options = { + chart: { + panKey: 'shift', + panning: { + enabled: true, + }, + type: 'line', + zooming: { + type: 'x', + }, + }, // Disable credits (attribution) in the chart credits: { enabled: false }, // Set chart title @@ -59,15 +69,27 @@ export function useRatingHistoryOptions( const ratingField: string = get(trackHistory[0], 'rating') ? 'rating' : 'newRating' // Configure series for the chart + options.plotOptions = { + ...options.plotOptions, + line: { + zones: TC_RATING_COLORS.map(tcColor => ({ + color: tcColor.color, + value: tcColor.limit, + })), + }, + } + options.series = [{ + color: 'transparent', data: trackHistory.sort((a, b) => get(b, dateField) - get(a, dateField)) .map((challenge: StatsHistory) => ({ + color: getRatingColor(challenge.newRating ?? challenge.rating), name: challenge.challengeName, x: get(challenge, dateField), y: get(challenge, ratingField), })), name: seriesName, - type: 'spline', + type: 'line', }] return options diff --git a/src/config/environments/default.env.ts b/src/config/environments/default.env.ts index ab4b3a90f..0b3d0c3fe 100644 --- a/src/config/environments/default.env.ts +++ b/src/config/environments/default.env.ts @@ -48,6 +48,7 @@ export const STRIPE = { export const URLS = { ACCOUNT_SETTINGS: `https://account-settings.${TC_DOMAIN}/#account`, + CHALLENGES_PAGE: `${TOPCODER_URL}/challenges`, UNIVERSAL_NAV: `https://uni-nav.${TC_DOMAIN}/v1/tc-universal-nav.js`, USER_PROFILE: `https://profiles.${TC_DOMAIN}`, } diff --git a/src/config/environments/global-config.model.ts b/src/config/environments/global-config.model.ts index b47a2b4ad..2f27a5ef4 100644 --- a/src/config/environments/global-config.model.ts +++ b/src/config/environments/global-config.model.ts @@ -26,6 +26,7 @@ export interface GlobalConfig { USER_PROFILE: string ACCOUNT_SETTINGS: string UNIVERSAL_NAV: string + CHALLENGES_PAGE: string } MEMBER_VERIFY_LOOKER: number ENABLE_TCA_CERT_MONETIZATION: boolean From 0a207618029f0cdd8d402cda935d253876788007 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 22 Nov 2023 10:31:25 +0200 Subject: [PATCH 24/37] MP-356 - fix talent search result routing --- .../MemberStatsBlock/MemberStatsBlock.tsx | 6 ++++-- .../member-profile/MemberProfile.context.tsx | 12 +++++++++--- .../sub-track-view/SubTrackView.tsx | 8 +++++--- .../tc-achievements/track-view/TrackView.tsx | 9 +++++---- src/apps/profiles/src/profiles.routes.tsx | 8 ++++++-- .../src/routes/talent-page/TalentPage.tsx | 2 ++ .../talent-search/src/talent-search.routes.tsx | 17 +++++++++++++++++ 7 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx index f86ff64d8..fd71b0a14 100644 --- a/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx +++ b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx @@ -6,8 +6,8 @@ import { UserProfile } from '~/libs/core' import { IconOutline } from '~/libs/ui' import { useFetchActiveTracks } from '../../../hooks' -import { getUserProfileStatsRoute } from '../../../profiles.routes' import { WinnerIcon } from '../../../lib' +import { MemberProfileContextValue, useMemberProfileContext } from '../../../member-profile/MemberProfile.context' import styles from './MemberStatsBlock.module.scss' @@ -16,6 +16,8 @@ interface MemberStatsBlockProps { } const MemberStatsBlock: FC = props => { + const { statsRoute }: MemberProfileContextValue = useMemberProfileContext() + const activeTracks = useFetchActiveTracks(props.profile.handle) return activeTracks?.length === 0 ? <> : ( @@ -30,7 +32,7 @@ const MemberStatsBlock: FC = props => {
    {activeTracks.map((track: any) => ( diff --git a/src/apps/profiles/src/member-profile/MemberProfile.context.tsx b/src/apps/profiles/src/member-profile/MemberProfile.context.tsx index 19bcd1325..9f8095adb 100644 --- a/src/apps/profiles/src/member-profile/MemberProfile.context.tsx +++ b/src/apps/profiles/src/member-profile/MemberProfile.context.tsx @@ -2,16 +2,21 @@ import { createContext, FC, ReactNode, useContext, useMemo } from 'react' import { UserSkill } from '~/libs/core' +import { getUserProfileStatsRoute } from '../profiles.routes' + export interface MemberProfileContextValue { isTalentSearch?: boolean skillsRenderer?: ( skills: Pick[] ) => ReactNode + statsRoute: typeof getUserProfileStatsRoute } -const MemberProfileRC = createContext({}) +const MemberProfileRC = createContext({ + statsRoute: getUserProfileStatsRoute, +}) -interface MemberProfileContextProps extends MemberProfileContextValue { +interface MemberProfileContextProps extends Partial { children?: ReactNode } @@ -19,7 +24,8 @@ export const MemberProfileContext: FC = props => { const contextValue = useMemo(() => ({ isTalentSearch: props.isTalentSearch, skillsRenderer: props.skillsRenderer, - }), [props.isTalentSearch, props.skillsRenderer]) + statsRoute: props.statsRoute ?? getUserProfileStatsRoute, + }), [props.statsRoute, props.isTalentSearch, props.skillsRenderer]) return ( = props => { + const { statsRoute }: MemberProfileContextValue = useMemberProfileContext() const params = useParams() + const { trackData, ...subTrackData }: any = useFetchSubTrackData(props.profile.handle, params.trackType, params.subTrack) @@ -28,8 +30,8 @@ const SubTrackView: FC = props => { {subTrackData.name === 'MARATHON_MATCH' || subTrackData.name === 'SRM' ? ( diff --git a/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx b/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx index a13d75506..148b3165d 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx +++ b/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx @@ -4,9 +4,9 @@ import { Link, useParams } from 'react-router-dom' import { MemberStats, UserProfile } from '~/libs/core' import { useFetchTrackData } from '../../../hooks' -import { getUserProfileRoute, getUserProfileStatsRoute } from '../../../profiles.routes' import { StatsDetailsLayout } from '../../../components/tc-achievements/StatsDetailsLayout' import { SubTrackSummaryCard } from '../../../components/tc-achievements/SubTrackSummaryCard' +import { MemberProfileContextValue, useMemberProfileContext } from '../../MemberProfile.context' import styles from './TrackView.module.scss' @@ -16,6 +16,7 @@ interface TrackViewProps { } const TrackView: FC = props => { + const { statsRoute }: MemberProfileContextValue = useMemberProfileContext() const params = useParams() const trackData = useFetchTrackData(props.profile.handle, params.trackType) @@ -24,15 +25,15 @@ const TrackView: FC = props => {
    {trackData.subTracks.map((subTrack: MemberStats) => ( ( `${rootRoute}${!userHandle ? '' : `/${userHandle.toLowerCase()}`}` ) -export const getUserProfileStatsRoute = (userHandle: string, track: string, subTrack?: string): string => ( - `${getUserProfileRoute(userHandle)}/stats/${track}${!subTrack ? '' : `/${subTrack}`}` +export const getUserProfileStatsRoute = ( + userHandle: string, + track?: string, + subTrack?: string, +): string => ( + `${getUserProfileRoute(userHandle)}${track ? `/stats/${track}` : ''}${!(track && subTrack) ? '' : `/${subTrack}`}` ) export const profilesRoutes: ReadonlyArray = [ diff --git a/src/apps/talent-search/src/routes/talent-page/TalentPage.tsx b/src/apps/talent-search/src/routes/talent-page/TalentPage.tsx index d93dae09f..ac77bc9c8 100644 --- a/src/apps/talent-search/src/routes/talent-page/TalentPage.tsx +++ b/src/apps/talent-search/src/routes/talent-page/TalentPage.tsx @@ -5,6 +5,7 @@ import { MemberProfileContext, MemberProfilePage } from '@profiles/member-profil import { UserSkill } from '~/libs/core' import { ProfileSkillsMatch } from '../../components/profile-skills-match' +import { getTalentStatsRoute } from '../../talent-search.routes' const TalentPage: FC = () => { const { state }: Location = useLocation() @@ -22,6 +23,7 @@ const TalentPage: FC = () => { return ( diff --git a/src/apps/talent-search/src/talent-search.routes.tsx b/src/apps/talent-search/src/talent-search.routes.tsx index fbc7e5cb6..021eaca94 100644 --- a/src/apps/talent-search/src/talent-search.routes.tsx +++ b/src/apps/talent-search/src/talent-search.routes.tsx @@ -34,6 +34,18 @@ export const toolTitle: string = ToolTitle.talentSearch const isAdminRestricted = EnvironmentConfig.RESTRICT_TALENT_SEARCH +export const getTalentRoute = (userHandle: string): string => ( + `${rootRoute}/talent/${userHandle.toLowerCase()}` +) + +export const getTalentStatsRoute = ( + userHandle: string, + track?: string, + subTrack?: string, +): string => ( + `${getTalentRoute(userHandle)}${track ? `/stats/${track}` : ''}${!(track && subTrack) ? '' : `/${subTrack}`}` +) + export const talentSearchRoutes: ReadonlyArray = [ { authRequired: isAdminRestricted, @@ -50,6 +62,11 @@ export const talentSearchRoutes: ReadonlyArray = [ element: , route: '/talent/:memberHandle', }, + { + element: , + id: 'MemberProfilePageSub', + route: '/talent/:memberHandle/stats/*', + }, { element: , id: 'MemberBadgesPage', From 817b63057829269ebe1cb7c04b07c09f26ea8463 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 23 Nov 2023 10:07:37 +0200 Subject: [PATCH 25/37] MP-374 - show stats for members that have no wins but have challenge history --- .../src/member-profile/tc-achievements/MemberTCAchievements.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx b/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx index abec84689..5bf7ee428 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx +++ b/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx @@ -47,7 +47,7 @@ const MemberTCAchievements: FC = (props: MemberTCAchi /> ), [memberStats, props.profile, tcoQualifications, tcoTrips, tcoWins]) - if (!memberStats?.wins && !tcoWins && !tcoQualifications) { + if (!memberStats?.challenges && !tcoWins && !tcoQualifications) { return <> } From febbf199e3cff74832f05f9cf922270d8ba56bb3 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 23 Nov 2023 10:08:23 +0200 Subject: [PATCH 26/37] MP-375 - order competitive track last --- src/apps/profiles/src/hooks/useFetchActiveTracks.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx index 5834bbd36..bcf485576 100644 --- a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx +++ b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx @@ -1,5 +1,5 @@ import { useMemo } from 'react' -import { filter, find, orderBy } from 'lodash' +import { filter, find, get, orderBy } from 'lodash' import { MemberStats, SRMStats, useMemberStats, UserStats } from '~/libs/core' @@ -14,6 +14,7 @@ export interface MemberStatsTrack { subTracks: MemberStats[], ranking?: number, wins: number, + order?: number } /** @@ -36,6 +37,7 @@ const buildTrackData = (trackName: string, subTracks: MemberStats[]): MemberStat challenges: challengesCount, isActive: challengesCount > 0, name: trackName, + order: 1, submissions: submissionsCount, subTracks, wins: totalWins, @@ -150,6 +152,7 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => challenges: memberStats?.DATA_SCIENCE?.challenges ?? 0, isActive: (memberStats?.DATA_SCIENCE?.challenges ?? 0) > 0, name: 'Competitive Programming', + order: -1, ranking: Math.max( dataScienceSubTracks.MARATHON_MATCH?.rank?.percentile ?? 0, dataScienceSubTracks.SRM?.rank?.percentile ?? 0, @@ -165,7 +168,7 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => designTrackStats, developTrackStats, testingTrackStats, - ], { isActive: true }), ['wins', 'submissions'], 'desc') + ], { isActive: true }), ['order', 'wins', 'submissions'], ['desc', 'desc', 'desc']) } /** @@ -208,7 +211,7 @@ export const useFetchSubTrackData = ( return undefined } - const subTrackData = find(trackData?.subTracks, { name: subTrack }) + const subTrackData = find(get(trackData, 'subTracks'), { name: subTrack }) return { ...subTrackData, trackData, From 0704114de74a6a0605d9e468220ed40bd57e63a4 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 23 Nov 2023 10:16:00 +0200 Subject: [PATCH 27/37] MP-376 - do not show wins for CP track --- .../tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx index fd71b0a14..be0a40870 100644 --- a/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx +++ b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx @@ -38,7 +38,7 @@ const MemberStatsBlock: FC = props => { > {track.name}
    - {!track.ranking && ((track.submissions || track.wins) > 0) && ( + {track.ranking === undefined && ((track.submissions || track.wins) > 0) && ( <> From 1c971c693a218e637902452807415bdce9f6c3e2 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 23 Nov 2023 10:25:59 +0200 Subject: [PATCH 28/37] MP-378 - update order for 2nd level subtracks --- .../tc-achievements/track-view/TrackView.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx b/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx index 148b3165d..499de0581 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx +++ b/src/apps/profiles/src/member-profile/tc-achievements/track-view/TrackView.tsx @@ -1,5 +1,6 @@ -import { FC, ReactElement } from 'react' +import { FC, ReactElement, useMemo } from 'react' import { Link, useParams } from 'react-router-dom' +import { orderBy } from 'lodash' import { MemberStats, UserProfile } from '~/libs/core' @@ -20,6 +21,12 @@ const TrackView: FC = props => { const params = useParams() const trackData = useFetchTrackData(props.profile.handle, params.trackType) + const subTracks: MemberStats[] = useMemo(() => orderBy( + trackData?.subTracks, + ['wins', 'submissions.submissions', 'challenges'], + ['desc', 'desc', 'desc'], + ), [trackData?.subTracks]) + return !trackData ? props.renderDefault() : (
    = props => { >
    - {trackData.subTracks.map((subTrack: MemberStats) => ( + {subTracks.map((subTrack: MemberStats) => ( Date: Thu, 23 Nov 2023 10:28:45 +0200 Subject: [PATCH 29/37] MP-379 - fix design submissions count --- src/apps/profiles/src/hooks/useFetchActiveTracks.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx index bcf485576..1a8cb05c8 100644 --- a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx +++ b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx @@ -29,7 +29,7 @@ const buildTrackData = (trackName: string, subTracks: MemberStats[]): MemberStat const totalWins = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.wins || 0)), 0) const challengesCount = subTracks.reduce((sum, subTrack) => (sum + (subTrack?.challenges || 0)), 0) const submissionsCount = subTracks.reduce((sum, subTrack) => ( - sum + (subTrack?.submissions?.submissions || 0) + sum + (subTrack?.submissions?.submissions ?? subTrack?.submissions ?? 0) ), 0) // Return aggregated track data From 532cdaf20844ec025cbf708550f60d5ab25db2ab Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 23 Nov 2023 12:36:42 +0200 Subject: [PATCH 30/37] MP-363 - add control for which stats we show for each track --- .../StatsDetailsLayout/StatsDetailsLayout.tsx | 3 +- .../StatsSummaryBlock/StatsSummaryBlock.tsx | 218 ++++++++++-------- src/apps/profiles/src/config/index.ts | 1 + .../src/config/tracks-summary-stats.ts | 43 ++++ .../src/hooks/useFetchActiveTracks.tsx | 61 +++-- src/apps/profiles/src/lib/math.utils.ts | 39 ++++ src/libs/core/lib/profile/user-stats.model.ts | 1 + 7 files changed, 254 insertions(+), 112 deletions(-) create mode 100644 src/apps/profiles/src/config/tracks-summary-stats.ts create mode 100644 src/apps/profiles/src/lib/math.utils.ts diff --git a/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx index e59017e8e..9e011b3cc 100644 --- a/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx +++ b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx @@ -35,9 +35,10 @@ const StatsDetailsLayout: FC = props => ( wins={props.trackData.wins} submissions={(props.trackData as MemberStats).submissions?.submissions ?? props.trackData.submissions} ranking={(props.trackData as MemberStatsTrack).ranking} - rating={(props.trackData as MemberStats).rank?.rating} + rating={(props.trackData as MemberStats).rank?.rating ?? (props.trackData as MemberStatsTrack).rating} volatility={(props.trackData as MemberStats).rank?.volatility} screeningSuccessRate={(props.trackData as MemberStats).screeningSuccessRate} + submissionRate={(props.trackData as MemberStats).submissionRate} percentile={(props.trackData as MemberStats).rank?.overallPercentile} />
    diff --git a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx index c88a29529..9da017862 100644 --- a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx +++ b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx @@ -1,8 +1,10 @@ +/* eslint-disable complexity */ import { FC } from 'react' import { getRatingColor } from '~/libs/core' import { numberToFixed } from '../../../lib' +import { TracksSummaryStats } from '../../../config' import styles from './StatsSummaryBlock.module.scss' @@ -15,110 +17,130 @@ interface StatsSummaryBlockProps { rating?: number percentile?: number screeningSuccessRate?: number + submissionRate?: number volatility?: number } -const StatsSummaryBlock: FC = props => ( -
    -
    - - {props.trackTitle} -  Stats - -
    -
    -
    - - {props.challenges} - - - - Challenges - +const StatsSummaryBlock: FC = props => { + const visibleFields = TracksSummaryStats[props.trackTitle]?.fields + + return ( +
    +
    + + {props.trackTitle} +  Stats
    -
    - - {props.wins} - - - - Wins - - +
    + {(!visibleFields || visibleFields.challenges) && ( +
    + + {props.challenges} + + + + Challenges + + +
    + )} + {(!visibleFields || visibleFields.wins) && ( +
    + + {props.wins} + + + + Wins + + +
    + )} + {(!visibleFields || visibleFields.submissions) && props.submissions !== undefined && ( +
    + + {props.submissions} + + + + Submissions + + +
    + )} + {(!visibleFields || visibleFields.ranking) && props.ranking !== undefined && ( +
    + + {numberToFixed(props.ranking)} + % + + + + Percentile + + +
    + )} + {(!visibleFields || visibleFields.rating) && props.rating !== undefined && ( +
    + + {props.rating} + + + rating + +
    + )} + {(!visibleFields || visibleFields.volatility) && props.volatility !== undefined && ( +
    + + {props.volatility} + + + volatility + +
    + )} + {(!visibleFields || visibleFields.screeningSuccessRate) && props.screeningSuccessRate !== undefined && ( +
    + + {numberToFixed(props.screeningSuccessRate * 100)} + % + + + Screening Success Rate + +
    + )} + {(!visibleFields || visibleFields.submissionRate) && props.submissionRate !== undefined && ( +
    + + {numberToFixed(props.submissionRate * 100)} + % + + + Submission Rate + +
    + )} + {(!visibleFields || visibleFields.percentile) && props.percentile !== undefined && ( +
    + + {numberToFixed(props.percentile)} + % + + + Percentile + +
    + )}
    - {props.submissions !== undefined && ( -
    - - {props.submissions} - - - - Submissions - - -
    - )} - {props.ranking !== undefined && ( -
    - - {numberToFixed(props.ranking)} - % - - - - Percentile - - -
    - )} - {props.volatility !== undefined && ( -
    - - {props.volatility} - - - volatility - -
    - )} - {props.rating !== undefined && ( -
    - - {props.rating} - - - rating - -
    - )} - {props.screeningSuccessRate !== undefined && ( -
    - - {numberToFixed(props.screeningSuccessRate * 100)} - % - - - Screening Success Rate - -
    - )} - {props.percentile !== undefined && ( -
    - - {numberToFixed(props.percentile)} - % - - - Percentile - -
    - )}
    -
    -) + ) +} export default StatsSummaryBlock diff --git a/src/apps/profiles/src/config/index.ts b/src/apps/profiles/src/config/index.ts index f87cf0102..97eb77c26 100644 --- a/src/apps/profiles/src/config/index.ts +++ b/src/apps/profiles/src/config/index.ts @@ -1 +1,2 @@ export * from './constants' +export * from './tracks-summary-stats' diff --git a/src/apps/profiles/src/config/tracks-summary-stats.ts b/src/apps/profiles/src/config/tracks-summary-stats.ts new file mode 100644 index 000000000..233d7e6e8 --- /dev/null +++ b/src/apps/profiles/src/config/tracks-summary-stats.ts @@ -0,0 +1,43 @@ +interface TrackSummaryStats { + name: string + fields: {[key: string]: boolean} +} + +export const designTrackSummaryStats: TrackSummaryStats = { + fields: { + challenges: true, + screeningSuccessRate: true, + submissionRate: true, + }, + name: 'Design', +} + +export const developTrackSummaryStats: TrackSummaryStats = { + fields: { + challenges: true, + }, + name: 'Development', +} + +export const testingTrackSummaryStats: TrackSummaryStats = { + fields: { + challenges: true, + }, + name: 'Testing', +} + +export const cpTrackSummaryStats: TrackSummaryStats = { + fields: { + challenges: true, + ranking: true, + rating: true, + }, + name: 'Competitive Programming', +} + +export const TracksSummaryStats: {[key: string]: TrackSummaryStats} = { + [designTrackSummaryStats.name]: designTrackSummaryStats, + [developTrackSummaryStats.name]: developTrackSummaryStats, + [testingTrackSummaryStats.name]: testingTrackSummaryStats, + [cpTrackSummaryStats.name]: cpTrackSummaryStats, +} diff --git a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx index 1a8cb05c8..7994c64bb 100644 --- a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx +++ b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx @@ -3,6 +3,8 @@ import { filter, find, get, orderBy } from 'lodash' import { MemberStats, SRMStats, useMemberStats, UserStats } from '~/libs/core' +import { calcProportionalAverage } from '../lib/math.utils' + /** * The structure of a track for a member. */ @@ -12,7 +14,10 @@ export interface MemberStatsTrack { name: string, submissions?: number, subTracks: MemberStats[], + rating?: number, ranking?: number, + submissionRate?: number + screeningSuccessRate?: number wins: number, order?: number } @@ -44,6 +49,30 @@ const buildTrackData = (trackName: string, subTracks: MemberStats[]): MemberStat } } +const enhanceDesignTrackData = (trackData: MemberStatsTrack): MemberStatsTrack => { + const { subTracks, submissions = 0, challenges = 0 }: MemberStatsTrack = trackData + + const submissionRate = calcProportionalAverage( + subTracks, + ['challenges'], + ['submissions.submissionRate', 'submissionRate'], + challenges, + ) + + const screeningSuccessRate = calcProportionalAverage( + subTracks, + ['submissions.submissions', 'submissions'], + ['submissions.screeningSuccessRate', 'screeningSuccessRate'], + submissions, + ) + + return { + ...trackData, + screeningSuccessRate, + submissionRate, + } +} + /** * Custom hook to fetch active tracks for a user, sorted by wins & submissions. * @@ -102,19 +131,21 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => // Design const designTrackStats: MemberStatsTrack = useMemo(() => ( - buildTrackData('Design', [ - developSubTracks.DESIGN, - designSubTracks.DESIGN_FIRST_2_FINISH, - designSubTracks.WEB_DESIGNS, - designSubTracks.LOGO_DESIGN, - designSubTracks.WIREFRAMES, - designSubTracks.FRONT_END_FLASH, - designSubTracks.PRINT_OR_PRESENTATION, - designSubTracks.STUDIO_OTHER, - designSubTracks.APPLICATION_FRONT_END_DESIGN, - designSubTracks.BANNERS_OR_ICONS, - designSubTracks.WIDGET_OR_MOBILE_SCREEN_DESIGN, - ].filter(Boolean)) + enhanceDesignTrackData( + buildTrackData('Design', [ + developSubTracks.DESIGN, + designSubTracks.DESIGN_FIRST_2_FINISH, + designSubTracks.WEB_DESIGNS, + designSubTracks.LOGO_DESIGN, + designSubTracks.WIREFRAMES, + designSubTracks.FRONT_END_FLASH, + designSubTracks.PRINT_OR_PRESENTATION, + designSubTracks.STUDIO_OTHER, + designSubTracks.APPLICATION_FRONT_END_DESIGN, + designSubTracks.BANNERS_OR_ICONS, + designSubTracks.WIDGET_OR_MOBILE_SCREEN_DESIGN, + ].filter(Boolean)), + ) ), [developSubTracks, designSubTracks]) // Development @@ -157,6 +188,10 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => dataScienceSubTracks.MARATHON_MATCH?.rank?.percentile ?? 0, dataScienceSubTracks.SRM?.rank?.percentile ?? 0, ), + rating: Math.max( + dataScienceSubTracks.MARATHON_MATCH?.rank?.rating ?? 0, + dataScienceSubTracks.SRM?.rank?.rating ?? 0, + ), subTracks, wins: memberStats?.DATA_SCIENCE?.wins ?? 0, } diff --git a/src/apps/profiles/src/lib/math.utils.ts b/src/apps/profiles/src/lib/math.utils.ts new file mode 100644 index 000000000..b75141c51 --- /dev/null +++ b/src/apps/profiles/src/lib/math.utils.ts @@ -0,0 +1,39 @@ +import { get } from 'lodash' + +/** + * Get a numeric field value from an item based on the provided possible existing fields. + * + * @param {any} item - The item to retrieve the field value from. + * @param {string[]} fields - An array of possible field names to retrieve value from. + * @returns {number} The retrieved field value or 0 if not found. + */ +const getFieldValue = (item: any, fields: string[]): number => ( + fields + .map(f => get(item, f)) + .filter(Boolean)[0] ?? 0 +) + +/** + * Calculate the proportional average based on specified fields and total count. + * + * @param {any[]} items - An array of items to calculate the proportional average from. + * @param {string[]} proportionalFields - Fields used to calculate the proportion. + * @param {string[]} valueFields - Fields used to get values for the calculation. + * @param {number} totalCount - The total count used for proportion calculation. + * @returns {number} The calculated proportional average. + */ +export const calcProportionalAverage = ( + items: any[], + proportionalFields: string[], + valueFields: string[], + totalCount: number, +): number => ( + items + .filter(s => getFieldValue(s, proportionalFields) > 0 && getFieldValue(s, valueFields) > 0) + .reduce((average, item) => { + const proportion = (getFieldValue(item, proportionalFields) as number) / totalCount + const proportionaItemlValue = getFieldValue(item, valueFields) * proportion + + return average + proportionaItemlValue + }, 0) +) diff --git a/src/libs/core/lib/profile/user-stats.model.ts b/src/libs/core/lib/profile/user-stats.model.ts index 5196fde3a..763d17212 100644 --- a/src/libs/core/lib/profile/user-stats.model.ts +++ b/src/libs/core/lib/profile/user-stats.model.ts @@ -45,6 +45,7 @@ export type MemberStats = { overallRank: number overallPercentile: number } + submissionRate: number screeningSuccessRate: number wins: number name: string From c4a3e23050b6d9e07f6732f92c66587bd9dec4f5 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Fri, 24 Nov 2023 10:43:03 +0200 Subject: [PATCH 31/37] MP-356 - minor fixes --- .../tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx | 2 +- src/apps/profiles/src/hooks/useFetchActiveTracks.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx index 9e011b3cc..6d01c7f21 100644 --- a/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx +++ b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx @@ -34,7 +34,7 @@ const StatsDetailsLayout: FC = props => ( challenges={props.trackData.challenges} wins={props.trackData.wins} submissions={(props.trackData as MemberStats).submissions?.submissions ?? props.trackData.submissions} - ranking={(props.trackData as MemberStatsTrack).ranking} + ranking={(props.trackData as MemberStats).rank?.rank ?? (props.trackData as MemberStatsTrack).ranking} rating={(props.trackData as MemberStats).rank?.rating ?? (props.trackData as MemberStatsTrack).rating} volatility={(props.trackData as MemberStats).rank?.volatility} screeningSuccessRate={(props.trackData as MemberStats).screeningSuccessRate} diff --git a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx index 7994c64bb..a34eff415 100644 --- a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx +++ b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx @@ -138,6 +138,7 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => designSubTracks.WEB_DESIGNS, designSubTracks.LOGO_DESIGN, designSubTracks.WIREFRAMES, + designSubTracks.IDEA_GENERATION, designSubTracks.FRONT_END_FLASH, designSubTracks.PRINT_OR_PRESENTATION, designSubTracks.STUDIO_OTHER, From 8658d57e15e1bbff049673bba677d8fc17a5fc4b Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 28 Nov 2023 10:04:16 +0200 Subject: [PATCH 32/37] TAL-94 - move namesAndHandleAppearance to member root object --- .../src/member-profile/about-me/AboutMe.tsx | 9 +- .../ModifyMemberNameModal.tsx | 36 ++---- .../profile-header/ProfileHeader.tsx | 119 +++++++----------- .../src/components/talent-card/TalentCard.tsx | 8 +- .../talent-search/src/lib/models/Member.ts | 5 +- .../talent-search/src/lib/models/index.ts | 1 - .../lib/profile/modify-user-profile.model.ts | 3 +- .../core/lib/profile/user-profile.model.ts | 7 ++ 8 files changed, 74 insertions(+), 114 deletions(-) diff --git a/src/apps/profiles/src/member-profile/about-me/AboutMe.tsx b/src/apps/profiles/src/member-profile/about-me/AboutMe.tsx index 69be73a23..64161ce38 100644 --- a/src/apps/profiles/src/member-profile/about-me/AboutMe.tsx +++ b/src/apps/profiles/src/member-profile/about-me/AboutMe.tsx @@ -3,7 +3,7 @@ import { useSearchParams } from 'react-router-dom' import { KeyedMutator } from 'swr' import classNames from 'classnames' -import { useMemberTraits, UserProfile, UserTrait, UserTraitIds, UserTraits } from '~/libs/core' +import { NamesAndHandleAppearance, useMemberTraits, UserProfile, UserTraitIds, UserTraits } from '~/libs/core' import { AddButton, EditMemberPropertyBtn, EmptySection } from '../../components' import { EDIT_MODE_QUERY_PARAM, profileEditModes } from '../../config' @@ -38,11 +38,6 @@ const AboutMe: FC = (props: AboutMeProps) => { props.profile && !props.profile.description ), [props.profile]) - const namesAndHandleAppearanceData: UserTrait | undefined - = useMemo(() => memberPersonalizationTraits?.[0]?.traits?.data?.find( - (trait: UserTrait) => trait.namesAndHandleAppearance, - ), [memberPersonalizationTraits]) - useEffect(() => { if (props.authProfile && editMode === profileEditModes.aboutMe) { setIsEditMode(true) @@ -76,7 +71,7 @@ const AboutMe: FC = (props: AboutMeProps) => { I'm {' '} { - namesAndHandleAppearanceData?.namesAndHandleAppearance === 'handleOnly' + props.profile.namesAndHandleAppearance === NamesAndHandleAppearance.handleOnly ? props.profile?.handle : props.profile?.firstName } diff --git a/src/apps/profiles/src/member-profile/profile-header/ModifyMemberNameModal/ModifyMemberNameModal.tsx b/src/apps/profiles/src/member-profile/profile-header/ModifyMemberNameModal/ModifyMemberNameModal.tsx index 9ec406888..acb19d051 100644 --- a/src/apps/profiles/src/member-profile/profile-header/ModifyMemberNameModal/ModifyMemberNameModal.tsx +++ b/src/apps/profiles/src/member-profile/profile-header/ModifyMemberNameModal/ModifyMemberNameModal.tsx @@ -1,27 +1,20 @@ import { Dispatch, FC, FocusEvent, SetStateAction, useState } from 'react' -import { reject, trim } from 'lodash' +import { trim } from 'lodash' import { toast } from 'react-toastify' import { + NamesAndHandleAppearance, updateMemberProfileAsync, - updateOrCreateMemberTraitsAsync, UserProfile, - UserTrait, - UserTraitCategoryNames, - UserTraitIds, } from '~/libs/core' import { BaseModal, Button, InputRadio, InputText } from '~/libs/ui' -import { NamesAndHandleAppearance } from '../ProfileHeader' - import styles from './ModifyMemberNameModal.module.scss' interface ModifyMemberNameModalProps { profile: UserProfile onClose: () => void onSave: () => void - memberPersonalizationTraitsData: UserTrait[] | undefined - namesAndHandleAppearance: NamesAndHandleAppearance | undefined } const ModifyMemberNameModal: FC = (props: ModifyMemberNameModalProps) => { @@ -43,10 +36,8 @@ const ModifyMemberNameModal: FC = (props: ModifyMemb const [currentLastName, setCurrentLastName]: [string, Dispatch>] = useState(props.profile.lastName) - const [namesAndHandleAppearance, setNamesAndHandleAppearance]: [ - NamesAndHandleAppearance | undefined, Dispatch> - ] - = useState(props.namesAndHandleAppearance) + const [namesAndHandleAppearance, setNamesAndHandleAppearance] + = useState(props.profile.namesAndHandleAppearance) function handleFirstNameChange(e: React.ChangeEvent): void { setCurrentFirstName(e.target.value) @@ -88,21 +79,12 @@ const ModifyMemberNameModal: FC = (props: ModifyMemb Promise.all([ updateMemberProfileAsync( props.profile.handle, - { firstName: updatedFirstName, lastName: updatedLastName }, - ), - updateOrCreateMemberTraitsAsync(props.profile.handle, [{ - categoryName: UserTraitCategoryNames.personalization, - traitId: UserTraitIds.personalization, - traits: { - data: [ - ...reject( - props.memberPersonalizationTraitsData, - (trait: any) => trait.namesAndHandleAppearance, - ), - { namesAndHandleAppearance }, - ], + { + firstName: updatedFirstName, + lastName: updatedLastName, + namesAndHandleAppearance: namesAndHandleAppearance as NamesAndHandleAppearance, }, - }]), + ), ]) .then(() => { toast.success('Your profile has been updated.', { position: toast.POSITION.BOTTOM_RIGHT }) diff --git a/src/apps/profiles/src/member-profile/profile-header/ProfileHeader.tsx b/src/apps/profiles/src/member-profile/profile-header/ProfileHeader.tsx index aae2aa896..da8c873b5 100644 --- a/src/apps/profiles/src/member-profile/profile-header/ProfileHeader.tsx +++ b/src/apps/profiles/src/member-profile/profile-header/ProfileHeader.tsx @@ -1,15 +1,11 @@ /* eslint-disable complexity */ import { Dispatch, FC, SetStateAction, useEffect, useMemo, useState } from 'react' import { Location, useLocation, useSearchParams } from 'react-router-dom' -import { KeyedMutator } from 'swr' import moment from 'moment' import { - useMemberTraits, + NamesAndHandleAppearance, UserProfile, - UserTrait, - UserTraitIds, - UserTraits, } from '~/libs/core' import { ProfilePicture, useCheckIsMobile } from '~/libs/shared' import { Button } from '~/libs/ui' @@ -30,8 +26,6 @@ interface ProfileHeaderProps { refreshProfile: (handle: string) => void } -export type NamesAndHandleAppearance = 'namesOnly' | 'handleOnly' | 'namesAndHandle' - const ProfileHeader: FC = (props: ProfileHeaderProps) => { const isMobile: boolean = useCheckIsMobile() @@ -48,18 +42,6 @@ const ProfileHeader: FC = (props: ProfileHeaderProps) => { const [queryParams]: [URLSearchParams, any] = useSearchParams() const editMode: string | null = queryParams.get(EDIT_MODE_QUERY_PARAM) - const { data: memberPersonalizationTraits, mutate: mutateTraits, loading: traitsLoading }: { - data: UserTraits[] | undefined, - mutate: KeyedMutator, - loading: boolean, - } - = useMemberTraits(props.profile.handle, { traitIds: UserTraitIds.personalization }) - - const namesAndHandleAppearanceData: UserTrait | undefined - = useMemo(() => memberPersonalizationTraits?.[0]?.traits?.data?.find( - (trait: UserTrait) => trait.namesAndHandleAppearance, - ), [memberPersonalizationTraits]) - const [isHiringFormOpen, setIsHiringFormOpen]: [boolean, Dispatch>] = useState(false) @@ -95,7 +77,6 @@ const ProfileHeader: FC = (props: ProfileHeaderProps) => { setTimeout(() => { setIsNameEditMode(false) props.refreshProfile(props.profile.handle) - mutateTraits() }, 1000) } @@ -159,60 +140,58 @@ const ProfileHeader: FC = (props: ProfileHeaderProps) => { !isMobile ? renderMemberPhotoWrap() : undefined } - {!traitsLoading && ( -
    -
    -
    -

    - { - namesAndHandleAppearanceData?.namesAndHandleAppearance === 'handleOnly' - ? props.profile.handle - : `${props.profile.firstName} ${props.profile.lastName?.slice(0, 1) ?? ''}` - } -

    +
    +
    +
    +

    { - canEdit && ( - - ) + props.profile.namesAndHandleAppearance === NamesAndHandleAppearance.handleOnly + ? props.profile.handle + : `${props.profile.firstName} ${props.profile.lastName?.slice(0, 1) ?? ''}` } -

    - -

    - { - // If the user hasn't set a name and handle appareance, display both name and handle - (namesAndHandleAppearanceData?.namesAndHandleAppearance === 'namesAndHandle' - || !namesAndHandleAppearanceData) ? ( - // eslint-disable-next-line react/jsx-indent - <> - {props.profile.handle} - {' '} - | - {' '} - - ) : undefined - } - Member Since - {' '} - {moment(props.profile.createdAt) - .format('MMM YYYY')}

    -
    - { - !canEdit && isTalentSearch ? ( -
    -
    - ) : undefined - } + ) + } +
    + +

    + { + // If the user hasn't set a name and handle appareance, display both name and handle + (props.profile.namesAndHandleAppearance === NamesAndHandleAppearance.both + || !props.profile.namesAndHandleAppearance) ? ( + // eslint-disable-next-line react/jsx-indent + <> + {props.profile.handle} + {' '} + | + {' '} + + ) : undefined + } + Member Since + {' '} + {moment(props.profile.createdAt) + .format('MMM YYYY')} +

    - )} + { + !canEdit && isTalentSearch ? ( +
    +
    + ) : undefined + } +
    { // Showing only when they can edit until we have the talent search app @@ -241,8 +220,6 @@ const ProfileHeader: FC = (props: ProfileHeaderProps) => { onClose={handleModifyNameModalClose} onSave={handleModifyNameModalSave} profile={props.profile} - memberPersonalizationTraitsData={memberPersonalizationTraits?.[0]?.traits?.data} - namesAndHandleAppearance={namesAndHandleAppearanceData?.namesAndHandleAppearance} /> ) } diff --git a/src/apps/talent-search/src/components/talent-card/TalentCard.tsx b/src/apps/talent-search/src/components/talent-card/TalentCard.tsx index bf0df7e12..d1ccb3067 100644 --- a/src/apps/talent-search/src/components/talent-card/TalentCard.tsx +++ b/src/apps/talent-search/src/components/talent-card/TalentCard.tsx @@ -6,10 +6,10 @@ import codes from 'country-calling-code' import { IconSolid } from '~/libs/ui' import { isSkillVerified, ProfilePicture, SkillPill } from '~/libs/shared' -import { UserSkill } from '~/libs/core' +import { NamesAndHandleAppearance, UserSkill } from '~/libs/core' import { ProfileMatch } from '../profile-match' -import { Member, MemberDisplayName } from '../../lib/models' +import { Member } from '../../lib/models' import { TALENT_SEARCH_PATHS } from '../../talent-search.routes' import { useIsMatchingSkill } from '../../lib/utils' @@ -94,14 +94,14 @@ const TalentCard: FC = props => {
    - {props.member.namesAndHandleAppearance !== MemberDisplayName.handleOnly && ( + {props.member.namesAndHandleAppearance !== NamesAndHandleAppearance.handleOnly && (
    {props.member.firstName} {' '} {props.member.lastName?.slice(0, 1) || ''}
    )} - {props.member.namesAndHandleAppearance !== MemberDisplayName.nameOnly && ( + {props.member.namesAndHandleAppearance !== NamesAndHandleAppearance.nameOnly && (
    {props.member.handle} diff --git a/src/apps/talent-search/src/lib/models/Member.ts b/src/apps/talent-search/src/lib/models/Member.ts index f1aab22d8..b29e61658 100644 --- a/src/apps/talent-search/src/lib/models/Member.ts +++ b/src/apps/talent-search/src/lib/models/Member.ts @@ -1,6 +1,5 @@ -import { UserSkill } from '~/libs/core' +import { NamesAndHandleAppearance, UserSkill } from '~/libs/core' -import { MemberDisplayName } from './MemberDisplayName' import MemberAddress from './MemberAddress' import MemberMaxRating from './MemberMaxRating' import MemberStats from './MemberStats' @@ -18,7 +17,7 @@ export default interface Member { handle: string; homeCountryCode: string; lastName: string; - namesAndHandleAppearance: MemberDisplayName + namesAndHandleAppearance: NamesAndHandleAppearance maxRating: MemberMaxRating; numberOfChallengesPlaced: number; numberOfChallengesWon: number; diff --git a/src/apps/talent-search/src/lib/models/index.ts b/src/apps/talent-search/src/lib/models/index.ts index 4179a1aca..7ac93e125 100644 --- a/src/apps/talent-search/src/lib/models/index.ts +++ b/src/apps/talent-search/src/lib/models/index.ts @@ -1,4 +1,3 @@ export type { default as Member } from './Member' export type { default as MemberMaxRating } from './MemberMaxRating' export type { default as MemberStats } from './MemberStats' -export { MemberDisplayName } from './MemberDisplayName' diff --git a/src/libs/core/lib/profile/modify-user-profile.model.ts b/src/libs/core/lib/profile/modify-user-profile.model.ts index ac85292d8..3316e085b 100644 --- a/src/libs/core/lib/profile/modify-user-profile.model.ts +++ b/src/libs/core/lib/profile/modify-user-profile.model.ts @@ -1,4 +1,4 @@ -import { TC_TRACKS } from './user-profile.model' +import { NamesAndHandleAppearance, TC_TRACKS } from './user-profile.model' export interface UpdateProfileRequest { addresses?: Array<{ @@ -15,6 +15,7 @@ export interface UpdateProfileRequest { lastName?: string tracks?: TC_TRACKS[], description?: string + namesAndHandleAppearance?: NamesAndHandleAppearance } export interface UserPhotoUpdateResponse { diff --git a/src/libs/core/lib/profile/user-profile.model.ts b/src/libs/core/lib/profile/user-profile.model.ts index 788b7e114..03d863bfd 100644 --- a/src/libs/core/lib/profile/user-profile.model.ts +++ b/src/libs/core/lib/profile/user-profile.model.ts @@ -2,6 +2,12 @@ import { UserSkill } from './user-skill.model' export type TC_TRACKS = 'DEVELOP' | 'DESIGN' | 'DATA_SCIENCE' +export enum NamesAndHandleAppearance { + both = 'namesAndHandle', + handleOnly = 'handleOnly', + nameOnly = 'namesOnly', +} + export interface UserProfile { addresses?: Array<{ city?: string @@ -34,4 +40,5 @@ export interface UserProfile { tracks?: Array updatedAt: number userId: number + namesAndHandleAppearance: NamesAndHandleAppearance } From be6b3fb00f11a207a62e79ca33a6ec46b7f9aacd Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 28 Nov 2023 10:49:39 +0200 Subject: [PATCH 33/37] MP-356 - split DataScience and CP tracks --- .../MemberStatsBlock/MemberStatsBlock.tsx | 8 ++-- .../StatsDetailsLayout/StatsDetailsLayout.tsx | 7 ++- .../StatsSummaryBlock/StatsSummaryBlock.tsx | 5 +-- .../src/hooks/useFetchActiveTracks.tsx | 44 +++++++++++++------ 4 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx index be0a40870..e7caecf60 100644 --- a/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx +++ b/src/apps/profiles/src/components/tc-achievements/MemberStatsBlock/MemberStatsBlock.tsx @@ -38,7 +38,7 @@ const MemberStatsBlock: FC = props => { > {track.name}
    - {track.ranking === undefined && ((track.submissions || track.wins) > 0) && ( + {!track.isDSTrack && ((track.submissions || track.wins) > 0) && ( <> @@ -52,14 +52,14 @@ const MemberStatsBlock: FC = props => { )} {/* competitive programming only */} - {track.ranking !== undefined && ( + {track.isDSTrack && ( - {track.ranking} + {track.percentile} % - Ranking + Percentile )} diff --git a/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx index 6d01c7f21..6c3759180 100644 --- a/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx +++ b/src/apps/profiles/src/components/tc-achievements/StatsDetailsLayout/StatsDetailsLayout.tsx @@ -34,12 +34,15 @@ const StatsDetailsLayout: FC = props => ( challenges={props.trackData.challenges} wins={props.trackData.wins} submissions={(props.trackData as MemberStats).submissions?.submissions ?? props.trackData.submissions} - ranking={(props.trackData as MemberStats).rank?.rank ?? (props.trackData as MemberStatsTrack).ranking} + ranking={(props.trackData as MemberStats).rank?.rank} rating={(props.trackData as MemberStats).rank?.rating ?? (props.trackData as MemberStatsTrack).rating} volatility={(props.trackData as MemberStats).rank?.volatility} screeningSuccessRate={(props.trackData as MemberStats).screeningSuccessRate} submissionRate={(props.trackData as MemberStats).submissionRate} - percentile={(props.trackData as MemberStats).rank?.overallPercentile} + percentile={( + (props.trackData as MemberStats).rank?.overallPercentile + ?? (props.trackData as MemberStats).rank?.percentile + )} />
    {props.children} diff --git a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx index 9da017862..f3bead3ab 100644 --- a/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx +++ b/src/apps/profiles/src/components/tc-achievements/StatsSummaryBlock/StatsSummaryBlock.tsx @@ -72,12 +72,11 @@ const StatsSummaryBlock: FC = props => { {(!visibleFields || visibleFields.ranking) && props.ranking !== undefined && (
    - {numberToFixed(props.ranking)} - % + {props.ranking} - Percentile + Rank
    diff --git a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx index a34eff415..15a8de16b 100644 --- a/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx +++ b/src/apps/profiles/src/hooks/useFetchActiveTracks.tsx @@ -20,6 +20,7 @@ export interface MemberStatsTrack { screeningSuccessRate?: number wins: number, order?: number + isDSTrack?: boolean } /** @@ -172,34 +173,49 @@ export const useFetchActiveTracks = (userHandle: string): MemberStatsTrack[] => ].filter(Boolean)) ), [developSubTracks]) + // Data science + const dsTrackStats: MemberStatsTrack = useMemo(() => { + // Aggregate stats for DATA SCIENCE track + const subTracks = [ + dataScienceSubTracks.MARATHON_MATCH, + ].filter(d => d?.challenges > 0) as MemberStats[] + + return { + challenges: dataScienceSubTracks.MARATHON_MATCH?.challenges ?? 0, + isActive: (dataScienceSubTracks.MARATHON_MATCH?.challenges ?? 0) > 0, + isDSTrack: true, + name: 'Data Science', + order: -1, + percentile: dataScienceSubTracks.MARATHON_MATCH?.rank?.percentile ?? 0, + rating: dataScienceSubTracks.MARATHON_MATCH?.rank?.rating ?? 0, + subTracks, + wins: dataScienceSubTracks.MARATHON_MATCH?.wins ?? 0, + } + }, [dataScienceSubTracks]) + // Competitive Programming const cpTrackStats: MemberStatsTrack = useMemo(() => { // Aggregate stats for Competitive Programming track const subTracks = [ - dataScienceSubTracks.MARATHON_MATCH, dataScienceSubTracks.SRM, ].filter(d => d?.challenges > 0) as MemberStats[] return { - challenges: memberStats?.DATA_SCIENCE?.challenges ?? 0, - isActive: (memberStats?.DATA_SCIENCE?.challenges ?? 0) > 0, + challenges: dataScienceSubTracks.SRM?.challenges ?? 0, + isActive: (dataScienceSubTracks.SRM?.challenges ?? 0) > 0, + isDSTrack: true, name: 'Competitive Programming', - order: -1, - ranking: Math.max( - dataScienceSubTracks.MARATHON_MATCH?.rank?.percentile ?? 0, - dataScienceSubTracks.SRM?.rank?.percentile ?? 0, - ), - rating: Math.max( - dataScienceSubTracks.MARATHON_MATCH?.rank?.rating ?? 0, - dataScienceSubTracks.SRM?.rank?.rating ?? 0, - ), + order: -2, + percentile: dataScienceSubTracks.SRM?.rank?.percentile ?? 0, + rating: dataScienceSubTracks.SRM?.rank?.rating ?? 0, subTracks, - wins: memberStats?.DATA_SCIENCE?.wins ?? 0, + wins: dataScienceSubTracks.SRM?.wins ?? 0, } - }, [dataScienceSubTracks, memberStats]) + }, [dataScienceSubTracks]) // Order and filter active tracks based on wins and submissions return orderBy(filter([ + dsTrackStats, cpTrackStats, designTrackStats, developTrackStats, From 1175e247883329d9ce836ae6af394d9ec2d3d9b7 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 29 Nov 2023 15:01:10 +0200 Subject: [PATCH 34/37] MP-356 - demo feedback --- .../ChallengeHistoryCard.module.scss | 29 +++++- .../ChallengeHistoryCard.tsx | 91 ++++++++++++------- .../ChallengeHistoryView.tsx | 7 +- .../DetailedTrackView/DetailedTrackView.tsx | 3 +- .../MemberStatsBlock.module.scss | 22 ++++- .../MemberStatsBlock/MemberStatsBlock.tsx | 48 +++++++--- .../tc-achievements/SRMView/SRMView.tsx | 1 + .../StatsSummaryBlock.module.scss | 6 +- .../StatsSummaryBlock/StatsSummaryBlock.tsx | 2 +- .../SubTrackSummaryCard.tsx | 37 +++----- .../src/hooks/useFetchActiveTracks.tsx | 11 ++- .../sub-track-view/SubTrackView.tsx | 16 +++- .../tc-achievements/track-view/TrackView.tsx | 3 +- 13 files changed, 185 insertions(+), 91 deletions(-) diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.module.scss b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.module.scss index db2821406..dd7556857 100644 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.module.scss +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.module.scss @@ -41,8 +41,8 @@ .statsItem { display: flex; - align-items: center; - gap: $sp-1; + align-items: baseline; + gap: 2px; &Value { color: $turq-160; @@ -55,9 +55,32 @@ &Label { color: $black-80; text-transform: capitalize; + font-family: $font-barlow-condensed; + font-weight: $font-weight-semibold; + font-size: 14px; + line-height: 22px; } &Icon { - margin-left: auto; + display: none; + &.placement-1, + &.placement-2, + &.placement-3, + &.placement-4 { + display: block; + } + + &.placement-1 path { + fill: $gold-1; + } + &.placement-2 path { + fill: $silver-1; + } + &.placement-3 path { + fill: $bronze-1; + } + &.placement-4 path { + fill: $turq-50; + } } } diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx index 87b074575..2ecf88826 100644 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryCard/ChallengeHistoryCard.tsx @@ -1,50 +1,75 @@ import { FC } from 'react' +import classNames from 'classnames' import { IconSolid } from '~/libs/ui' import { getRatingColor, StatsHistory } from '~/libs/core' import { EnvironmentConfig } from '~/config' +import { WinnerIcon } from '../../../../lib' + import styles from './ChallengeHistoryCard.module.scss' interface ChallengeHistoryCardProps { challenge: StatsHistory } -const ChallengeHistoryCard: FC = props => ( - -
    -
    - - {props.challenge.challengeName} - -
    -
    -
    - -
    -
    -) +
    + +
    + + ) +} export default ChallengeHistoryCard diff --git a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx index fbb15f67e..4b0f33cbd 100644 --- a/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx +++ b/src/apps/profiles/src/components/tc-achievements/ChallengeHistoryView/ChallengeHistoryView.tsx @@ -16,8 +16,13 @@ const ChallengeHistoryView: FC = props => { const trackHistory: StatsHistory[] = get( find(get(statsHistory, `${props.trackData.path}`, []), { name: props.trackData.name }), 'history', - [], + get( + statsHistory, + `${props.trackData.path}.${props.trackData.name}.history`, + [], + ), ) + return (
    diff --git a/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.tsx b/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.tsx index 21c5d210d..8be10c274 100644 --- a/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.tsx +++ b/src/apps/profiles/src/components/tc-achievements/DetailedTrackView/DetailedTrackView.tsx @@ -28,6 +28,7 @@ enum Graphs { } interface DetailedTrackViewProps { + challengesLabel?: string trackData?: SRMStats | MemberStats trackHistory?: StatsHistory[] ratingDistribution?: UserStatsDistributionResponse @@ -88,7 +89,7 @@ const DetailedTrackView: FC = props => { />