Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/apps/profiles/src/config/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// import { EnvironmentConfig } from '~/config'

import { UserRole } from '~/libs/core'

export enum TRACKS_PROFILE_MAP {
DEVELOP = 'Developer',
DESIGN = 'Designer',
Expand All @@ -26,3 +28,5 @@ export enum profileEditModes {
// (removed) CES Survey/Userflow integrations

export const MAX_PRINCIPAL_SKILLS_COUNT = 10

export const ADMIN_ROLES = [UserRole.administrator]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The ADMIN_ROLES array now only includes UserRole.administrator. Ensure that this change aligns with the intended functionality, as it removes the admin role from the list. If both roles are still needed, consider updating the UserRole import to include the admin role as well.

38 changes: 37 additions & 1 deletion src/apps/profiles/src/lib/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* eslint-disable complexity */
import { UserProfile } from '~/libs/core'
import { UserProfile, UserRole } from '~/libs/core'

import { ADMIN_ROLES } from '../config'

declare global {
interface Window { tcUniNav: any }
Expand Down Expand Up @@ -125,3 +127,37 @@ export function isValidURL(urlToValidate: string): boolean {
export function formatPlural(count: number, baseWord: string): string {
return `${baseWord}${count === 1 ? '' : 's'}`
}

/**
* Check if the user can download the profile
* @param authProfile - The authenticated user profile
* @param profile - The profile to check if the user can download
* @returns {boolean} - Whether the user can download the profile
*/
export function canDownloadProfile(authProfile: UserProfile | undefined, profile: UserProfile): boolean {
if (!authProfile) {
return false
}

// Check if user is viewing their own profile
if (authProfile.handle === profile.handle) {
return true
}

// Check if user has admin roles
if (authProfile.roles?.some(role => ADMIN_ROLES.includes(role.toLowerCase() as UserRole))) {
return true
}

// Check if user has PM or Talent Manager roles
const allowedRoles = ['Project Manager', 'Talent Manager']
if (authProfile
.roles?.some(
role => allowedRoles.some(allowed => role.toLowerCase() === allowed.toLowerCase()),
)
) {
return true
}

return false
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,68 @@
height: 100%;

.profileHeaderWrap {
position: relative;
background: url('../../lib/assets/profile-header-bg.png') no-repeat right top / auto, linear-gradient(#0d83c5, #0e89d5);

@include ltelg {
background: url('../../lib/assets/profile-header-bg-mobile.png') no-repeat right top /100% 100%;
}

.downloadButtonWrap {
position: absolute;
top: $sp-4;
right: calc((100% - #{$xxl-min}) / 2);
max-width: $xxl-min;
width: 100%;
display: flex;
justify-content: flex-end;
padding-right: $sp-8;
z-index: 10;

@include ltexl {
right: $sp-8;
}

@include ltemd {
padding-right: $sp-6;
right: $sp-6;
}

@include ltesm {
padding-right: $sp-4;
right: $sp-4;
}

@include ltelg {
position: absolute;
top: $sp-4;
right: $sp-4;
left: auto;
max-width: none;
width: auto;
padding: 0;
pointer-events: auto;
}

> * {
pointer-events: auto;
}
}

.downloadButton {
color: $tc-white;
padding: $sp-2 $sp-4;
border-radius: 4px;
font-weight: $font-weight-bold;
font-family: $font-roboto;
font-size: 16px;

&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}

.profileHeaderContent {
padding: 0;
max-height: 260px;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FC } from 'react'
import { Dispatch, FC, SetStateAction, useState } from 'react'

import { UserProfile } from '~/libs/core'
import { downloadProfileAsync, UserProfile } from '~/libs/core'
import { Button, ContentLayout, IconSolid, PageTitle } from '~/libs/ui'

// import { MemberTCActivityInfo } from '../tc-activity'
Expand All @@ -14,6 +14,7 @@ import { MemberTCAchievements } from '../tc-achievements'
import { WorkExpirence } from '../work-expirence'
import { EducationAndCertifications } from '../education-and-certifications'
import { ProfileCompleteness } from '../profile-completeness'
import { canDownloadProfile } from '../../lib'
import OnboardingCompleted from '../onboarding-complete/OnboardingCompleted'

import styles from './ProfilePageLayout.module.scss'
Expand All @@ -26,102 +27,137 @@ interface ProfilePageLayoutProps {
handleBackBtn: () => void
}

const ProfilePageLayout: FC<ProfilePageLayoutProps> = (props: ProfilePageLayoutProps) => (
<div className={styles.container}>

<PageTitle>{`${props.profile.handle} | Community Profile | Topcoder`}</PageTitle>

<div className={styles.profileHeaderWrap}>
<ContentLayout
outerClass={styles.profileHeaderContentOuter}
contentClass={styles.profileHeaderContent}
>
{props.isTalentSearch && (
<div className={styles.backBtn}>
<Button
link
label='Search Results'
icon={IconSolid.ChevronLeftIcon}
iconToLeft
onClick={props.handleBackBtn}
/>
</div>
)}
<ProfileHeader
profile={props.profile}
authProfile={props.authProfile}
refreshProfile={props.refreshProfile}
/>
</ContentLayout>
<div className={styles.profileHeaderBottom} />
</div>

<ContentLayout
outerClass={styles.profileOuter}
innerClass={styles.profileInner}
>
<div className={styles.profileInfoWrap}>
<div className={styles.profileInfoLeft}>
<AboutMe
const ProfilePageLayout: FC<ProfilePageLayoutProps> = (props: ProfilePageLayoutProps) => {

const canDownload: boolean = canDownloadProfile(props.authProfile, props.profile)

const [isDownloading, setIsDownloading]: [boolean, Dispatch<SetStateAction<boolean>>]
= useState<boolean>(false)

async function handleDownloadProfile(): Promise<void> {
if (isDownloading) {
return
}

setIsDownloading(true)
try {
await downloadProfileAsync(props.profile.handle)
} catch (error) {} finally {
setIsDownloading(false)
}
}

return (
<div className={styles.container}>

<PageTitle>{`${props.profile.handle} | Community Profile | Topcoder`}</PageTitle>

<div className={styles.profileHeaderWrap}>
{
canDownload && (
<div className={styles.downloadButtonWrap}>
<Button
label='Download Profile'
icon={IconSolid.DownloadIcon}
iconToRight
onClick={handleDownloadProfile}
disabled={isDownloading}
className={styles.downloadButton}
/>
</div>
)
}
<ContentLayout
outerClass={styles.profileHeaderContentOuter}
contentClass={styles.profileHeaderContent}
>
{props.isTalentSearch && (
<div className={styles.backBtn}>
<Button
link
label='Search Results'
icon={IconSolid.ChevronLeftIcon}
iconToLeft
onClick={props.handleBackBtn}
/>
</div>
)}
<ProfileHeader
profile={props.profile}
authProfile={props.authProfile}
refreshProfile={props.refreshProfile}
/>
</ContentLayout>
<div className={styles.profileHeaderBottom} />
</div>

<MemberLanguages profile={props.profile} authProfile={props.authProfile} />
<ContentLayout
outerClass={styles.profileOuter}
innerClass={styles.profileInner}
>
<div className={styles.profileInfoWrap}>
<div className={styles.profileInfoLeft}>
<AboutMe
profile={props.profile}
authProfile={props.authProfile}
refreshProfile={props.refreshProfile}
/>

<MemberLocalInfo
profile={props.profile}
authProfile={props.authProfile}
refreshProfile={props.refreshProfile}
/>
<MemberLanguages profile={props.profile} authProfile={props.authProfile} />

{props.profile.userId === props.authProfile?.userId && (
<MemberLinks profile={props.profile} authProfile={props.authProfile} />
)}
</div>
<div className={styles.profileInfoRight}>
{props.authProfile?.handle === props.profile.handle && (
<ProfileCompleteness profile={props.profile} authProfile={props.authProfile} />
)}
<div className={styles.sectionWrap}>
<div className={styles.skillsWrap}>
<MemberSkillsInfo
profile={props.profile}
authProfile={props.authProfile}
refreshProfile={props.refreshProfile}
/>
</div>
</div>
<MemberLocalInfo
profile={props.profile}
authProfile={props.authProfile}
refreshProfile={props.refreshProfile}
/>

<MemberTCAchievements profile={props.profile} />
{props.profile.userId === props.authProfile?.userId && (
<MemberLinks profile={props.profile} authProfile={props.authProfile} />
)}
</div>
<div className={styles.profileInfoRight}>
{props.authProfile?.handle === props.profile.handle && (
<ProfileCompleteness profile={props.profile} authProfile={props.authProfile} />
)}
<div className={styles.sectionWrap}>
<div className={styles.skillsWrap}>
<MemberSkillsInfo
profile={props.profile}
authProfile={props.authProfile}
refreshProfile={props.refreshProfile}
/>
</div>
</div>

<div className={styles.expirenceWrap}>
<div>
<MemberTCAchievements profile={props.profile} />

<div className={styles.expirenceWrap}>
<div>
<div className={styles.sectionWrap}>
<WorkExpirence
profile={props.profile}
authProfile={props.authProfile}
refreshProfile={props.refreshProfile}
/>
</div>
</div>
<div className={styles.sectionWrap}>
<WorkExpirence
<EducationAndCertifications
profile={props.profile}
authProfile={props.authProfile}
refreshProfile={props.refreshProfile}
/>
</div>
</div>
<div className={styles.sectionWrap}>
<EducationAndCertifications
profile={props.profile}
authProfile={props.authProfile}
refreshProfile={props.refreshProfile}
/>
</div>
</div>
</div>
</div>

</ContentLayout>
</ContentLayout>

<OnboardingCompleted authProfile={props.authProfile} />
<OnboardingCompleted authProfile={props.authProfile} />

</div>
)
</div>
)
}

export default ProfilePageLayout
1 change: 1 addition & 0 deletions src/libs/core/lib/profile/profile-functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export {
modifyTracksAsync,
updateMemberProfileAsync,
updateMemberPhotoAsync,
downloadProfileAsync,
updateOrCreateMemberTraitsAsync,
updateDeleteOrCreateMemberTraitAsync,
} from './profile.functions'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { xhrDeleteAsync, xhrGetAsync, xhrPatchAsync, xhrPostAsync, xhrPutAsync } from '../../../xhr'
import { xhrDeleteAsync, xhrGetAsync, xhrGetBlobAsync, xhrPatchAsync, xhrPostAsync, xhrPutAsync } from '../../../xhr'
import { CountryLookup } from '../../country-lookup.model'
import { EditNameRequest } from '../../edit-name-request.model'
import { ModifyTracksRequest } from '../../modify-tracks.request'
Expand Down Expand Up @@ -126,3 +126,7 @@ export async function updateMemberPhoto(handle: string, payload: FormData): Prom
},
})
}

export async function downloadProfile(handle: string): Promise<Blob> {
return xhrGetBlobAsync<Blob>(`${profileUrl(handle)}/profileDownload`)
}
Loading
Loading