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
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Dialog which shows incomplete user profile.
*/
import React from 'react'
import PT from 'prop-types'
import Modal from 'react-modal'
import IncompleteUserProfile from '../IncompleteUserProfile/IncompleteUserProfile'
import XMarkIcon from '../../assets/icons/icon-x-mark.svg'
import styles from './IncompleteUserProfileDialog.scss'
import LoadingIndicator from '../LoadingIndicator/LoadingIndicator'

const IncompleteUserProfileDialog = ({
onCloseDialog,
title,
...restProps,
}) => {
return (
<Modal
isOpen
className="project-dialog-conatiner"
overlayClassName="management-dialog-overlay incomplete-profile-dialog-overlay"
onRequestClose={onCloseDialog}
contentLabel=""
>
<div className={`project-dialog ${styles.dialog}`}>
<div className="dialog-title">
<h3>{title}</h3>
<p styleName="subtitle">Complete your profile now.</p>
<span onClick={onCloseDialog}><XMarkIcon /></span>
</div>

<div className={`dialog-body ${styles.body}`}>
{restProps.profileSettings.pending && <div styleName="loadingOverlay"><LoadingIndicator /></div>}
<IncompleteUserProfile
{...restProps}
submitButton="Save"
buttonExtraClassName="tc-btn-md"
/>
</div>
</div>
</Modal>
)
}

IncompleteUserProfileDialog.propTypes = {
profileSettings: PT.object.isRequired,
saveProfileSettings: PT.func.isRequired,
isTopcoderUser: PT.bool.isRequired,
user: PT.object.isRequired,
onCloseDialog: PT.func.isRequired,
title: PT.string.isRequired,
}

export default IncompleteUserProfileDialog
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
@import '~tc-ui/src/styles/tc-includes';
@import '../../styles/includes';

:global(.management-dialog-overlay .project-dialog-conatiner .project-dialog) {
&.dialog {
width: 800px;
}

:global(.dialog-body).body {
max-height: 500px;
padding-top: $base-unit * 4;
position: relative;
}
}

:global(.incomplete-profile-dialog-overlay.management-dialog-overlay .project-dialog-conatiner .project-dialog .input-container) {
display: block;
background: transparent;
border-top: 0;
border-radius: 0;
margin: 0;
padding: 0;

input {
margin: 0;
}

:global(.dropdown-wrap) {
margin: 0;
width: 100px;
}
}

.subtitle {
padding-top: $base-unit * 4;
text-align: center;
}

.loadingOverlay {
align-items: center;
background-color: #fff;
display: flex;
left: 0;
height: 100%;
justify-content: center;
position: absolute;
top: 0;
width: 100%;
z-index: 1;
}

@media screen and (max-width: $screen-md - 1px) {
:global(.management-dialog-overlay .project-dialog-conatiner .project-dialog) {
&.dialog {
width: 100%;
}
}
}

@media screen and (max-height: 700px) {
:global(.management-dialog-overlay .project-dialog-conatiner .project-dialog) {
:global(.dialog-body).body {
max-height: calc(100vh - 200px);
}
}
}
72 changes: 72 additions & 0 deletions src/components/IncompleteUserProfile/IncompleteUserProfile.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Incomplete User Profile Form.
*/
import React from 'react'
import PT from 'prop-types'
import { PROFILE_FIELDS_CONFIG } from '../../config/constants'
import ProfileSettingsForm from '../../routes/settings/routes/profile/components/ProfileSettingsForm'
import { getDefaultTopcoderRole } from '../../helpers/permissions'

const IncompleteUserProfile = ({
profileSettings,
saveProfileSettings,
isTopcoderUser,
user,
...restProps
}) => {
const fieldsConfig = isTopcoderUser ? PROFILE_FIELDS_CONFIG.TOPCODER : PROFILE_FIELDS_CONFIG.CUSTOMER
// never show avatar
delete fieldsConfig.avatar
// config the form to only show required fields which doesn't have the value yet
const missingFieldsConfig = _.reduce(fieldsConfig, (acc, isFieldRequired, fieldKey) => {
if (isFieldRequired && !_.get(profileSettings, `settings.${fieldKey}`)) {
acc[fieldKey] = isFieldRequired
}
return acc
}, {})

// prefill some fields of the profile
const prefilledProfileSettings = _.cloneDeep(profileSettings)

// if time zone is required and doesn't have a value yet,
// then detect timezone using browser feature and prefill the form
if (fieldsConfig.timeZone && !profileSettings.settings.timeZone) {
prefilledProfileSettings.settings.timeZone = (new Intl.DateTimeFormat()).resolvedOptions().timeZone
}

if (isTopcoderUser) {
// We don't ask Topcoder User for "Company Name" and "Title"
// but server requires them, so if they are not yet defined, we set them automatically
if (!profileSettings.settings.companyName) {
prefilledProfileSettings.settings.companyName = 'Topcoder'
}
if (!profileSettings.settings.title) {
prefilledProfileSettings.settings.title = getDefaultTopcoderRole(user)
}
} else {
// at the moment we don't let users to update their business email, so in case it's not set, use registration email
if (!profileSettings.settings.businessEmail) {
prefilledProfileSettings.settings.businessEmail = user.email
}
}

return (
<ProfileSettingsForm
{...restProps}
values={prefilledProfileSettings}
saveSettings={saveProfileSettings}
fieldsConfig={missingFieldsConfig}
shouldDoValidateOnStart
shouldShowTitle={false}
/>
)
}

IncompleteUserProfile.propTypes = {
profileSettings: PT.object.isRequired,
saveProfileSettings: PT.func.isRequired,
isTopcoderUser: PT.bool.isRequired,
user: PT.object.isRequired,
}

export default IncompleteUserProfile
79 changes: 78 additions & 1 deletion src/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,24 @@ export const MANAGER_ROLES = [
ROLE_PROJECT_MANAGER,
]

/**
* Is user has any of these roles, it means such a user is not a customer.
*/
export const NON_CUSTOMER_ROLES = [
ROLE_CONNECT_COPILOT,
ROLE_CONNECT_MANAGER,
ROLE_CONNECT_ACCOUNT_MANAGER,
ROLE_CONNECT_ADMIN,
ROLE_ADMINISTRATOR,
ROLE_CONNECT_COPILOT_MANAGER,
ROLE_BUSINESS_DEVELOPMENT_REPRESENTATIVE,
ROLE_PRESALES,
ROLE_ACCOUNT_EXECUTIVE,
ROLE_PROGRAM_MANAGER,
ROLE_SOLUTION_ARCHITECT,
ROLE_PROJECT_MANAGER,
]

// to be able to start the Connect App we should pass at least the dummy value for `FILE_PICKER_API_KEY`
// but if we want to test file uploading we should provide the real value in `FILE_PICKER_API_KEY` env variable
export const FILE_PICKER_API_KEY = process.env.FILE_PICKER_API_KEY || 'DUMMY'
Expand Down Expand Up @@ -1011,4 +1029,63 @@ export const INTERNAL_PROJECT_URLS=[
/**
* Project category string
*/
export const PROJECT_CATEGORY_TAAS = 'talent-as-a-service'
export const PROJECT_CATEGORY_TAAS = 'talent-as-a-service'

/**
* Config for User Profile fields
*
* - `true` means field is required
* - `false` means field is optional
* - if field is not on the list means it should not be shown
*/
export const PROFILE_FIELDS_CONFIG = {
// this config is used to show any user profile
DEFAULT: {
// required fields
firstName: true,
lastName: true,
title: true,
timeZone: true,
businessPhone: true,
companyName: true,

// optional fields
country: false,
avatar: false,
workingHourStart: false,
workingHourEnd: false,
},

// configs below are used when we ask users to fill missing fields (progressive registration)
TOPCODER: {
// required fields
firstName: true,
lastName: true,
country: true,
timeZone: true,
workingHourStart: true,
workingHourEnd: true,

// optional fields
avatar: false,
title: false,
companyName: false,
businessPhone: false,
},
CUSTOMER: {
// required fields
firstName: true,
lastName: true,
title: true,
companyName: true,
businessPhone: true,

// optional fields
businessEmail: false,
avatar: false,
country: false,
timeZone: false,
workingHourStart: false,
workingHourEnd: false,
}
}
32 changes: 30 additions & 2 deletions src/helpers/tcHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
DISCOURSE_BOT_USERID,
CODER_BOT_USERID,
TC_SYSTEM_USERID,
TC_CDN_URL
TC_CDN_URL,
NON_CUSTOMER_ROLES,
PROFILE_FIELDS_CONFIG,
} from '../config/constants'

/**
Expand Down Expand Up @@ -44,4 +46,30 @@ export const getFullNameWithFallback = (user) => {
userFullName = userFullName && userFullName.trim().length > 0 ? userFullName : user.handle
userFullName = userFullName && userFullName.trim().length > 0 ? userFullName : 'Connect user'
return userFullName
}
}

/**
* Check if user profile is complete or no.
*
* @param {Object} user `loadUser.user` from Redux Store
* @param {Object} profileSettings profile settings with traits
*
* @returns {Boolean} complete or no
*/
export const isUserProfileComplete = (user, profileSettings) => {
const isTopcoderUser = _.intersection(user.roles, NON_CUSTOMER_ROLES).length > 0
const fieldsConfig = isTopcoderUser ? PROFILE_FIELDS_CONFIG.TOPCODER : PROFILE_FIELDS_CONFIG.CUSTOMER

// check if any required field doesn't have a value
let isMissingUserInfo = false
_.forEach(_.keys(fieldsConfig), (fieldKey) => {
const isFieldRequired = fieldsConfig[fieldKey]

if (isFieldRequired && !profileSettings.fieldKey) {
isMissingUserInfo = true
return false
}
})

return !isMissingUserInfo
}
Loading