Skip to content

Commit 7a185cb

Browse files
authored
Merge pull request #2658 from appirio-tech/feature/connect_user_profile_settings
Feature/connect user profile settings
2 parents 36791e3 + 24b48e1 commit 7a185cb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2344
-575
lines changed

config/constants/dev.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,7 @@ module.exports = {
4646
TC_NOTIFICATION_URL: 'https://api.topcoder-dev.com/v5/notifications',
4747
CONNECT_MESSAGE_API_URL: 'https://api.topcoder-dev.com/v5',
4848
TC_SYSTEM_USERID: process.env.DEV_TC_SYSTEM_USERID,
49-
MAINTENANCE_MODE: process.env.DEV_MAINTENANCE_MODE
49+
MAINTENANCE_MODE: process.env.DEV_MAINTENANCE_MODE,
50+
51+
RESET_PASSWORD_URL: 'https://accounts.topcoder-dev.com/connect/reset-password'
5052
}

config/constants/master.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,7 @@ module.exports = {
4646
TC_NOTIFICATION_URL: 'https://api.topcoder.com/v5/notifications',
4747
CONNECT_MESSAGE_API_URL: 'https://api.topcoder.com/v5',
4848
TC_SYSTEM_USERID: process.env.PROD_TC_SYSTEM_USERID,
49-
MAINTENANCE_MODE: process.env.PROD_MAINTENANCE_MODE
49+
MAINTENANCE_MODE: process.env.PROD_MAINTENANCE_MODE,
50+
51+
RESET_PASSWORD_URL: 'https://accounts.topcoder.com/connect/reset-password'
5052
}

src/actions/loadUser.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ export function loadUserSuccess(dispatch, token) {
4949
if (currentUser) {
5050
getUserProfile(currentUser.handle).then((profile) => {
5151
currentUser = _.assign(currentUser, profile)
52-
// keeping profile for backward compatibility
53-
currentUser.profile = profile
5452
// determine user role
5553
let userRole
5654
if (_.indexOf(currentUser.roles, ROLE_ADMINISTRATOR) > -1) {

src/api/s3.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Amazon S3 Service API
3+
*/
4+
5+
/**
6+
* Upload file to S3 using pre-signed URL
7+
*
8+
* @param {String} preSignedURL pre-signed URL
9+
* @param {File} file file to upload
10+
*
11+
* @returns {Promise<String>} pre-signed URL
12+
*/
13+
export const uploadFileToS3 = (preSignedURL, file) => {
14+
return new Promise((resolve, reject) => {
15+
const xhr = new XMLHttpRequest()
16+
17+
xhr.open('PUT', preSignedURL, true)
18+
xhr.setRequestHeader('Content-Type', file.type)
19+
20+
xhr.onreadystatechange = () => {
21+
const { status } = xhr
22+
if (((status >= 200 && status < 300) || status === 304) && xhr.readyState === 4) {
23+
resolve(preSignedURL)
24+
} else if (status >= 400) {
25+
const err = new Error('Could not upload image')
26+
err.status = status
27+
reject(err)
28+
}
29+
}
30+
xhr.onerror = (err) => {
31+
reject(err)
32+
}
33+
xhr.send(file)
34+
})
35+
}

src/api/users.js

Lines changed: 123 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,135 @@
11
import _ from 'lodash'
22
import { axiosInstance as axios } from './requestInterceptor'
33
import { TC_API_URL } from '../config/constants'
4+
import { RESET_PASSWORD_URL } from '../../config/constants'
45

56
/**
6-
* Get a user basd on it's handle/username
7-
* @param {integer} handle unique identifier of the user
8-
* @return {object} user returned by api
7+
* Get a user based on it's handle/username
8+
*
9+
* @param {String} handle user handle
10+
*
11+
* @returns {Promise<Object>} user profile data
912
*/
1013
export function getUserProfile(handle) {
1114
return axios.get(`${TC_API_URL}/v3/members/${handle}/`)
1215
.then(resp => {
1316
return _.get(resp.data, 'result.content', {})
1417
})
1518
}
19+
20+
/**
21+
* Update user profile
22+
*
23+
* @param {String} handle user handle
24+
* @param {Object} updatedProfile updated user data
25+
*
26+
* @returns {Promise<Object>} user profile data
27+
*/
28+
export function updateUserProfile(handle, updatedProfile) {
29+
return axios.put(`${TC_API_URL}/v3/members/${handle}/`, {
30+
param: updatedProfile
31+
})
32+
.then(resp => {
33+
return _.get(resp.data, 'result.content', {})
34+
})
35+
}
36+
37+
/**
38+
* Get member traits
39+
*
40+
* @param {String} handle member handle
41+
*
42+
* @returns {Promise<Array>} member traits
43+
*/
44+
export const getMemberTraits = (handle) => {
45+
return axios.get(`${TC_API_URL}/v3/members/${handle}/traits`)
46+
.then(resp => _.get(resp.data, 'result.content', {}))
47+
}
48+
49+
/**
50+
* Update member traits
51+
*
52+
* @param {String} handle member handle
53+
* @param {Array} updatedTraits list of updated traits
54+
*
55+
* @returns {Promise<Array>} member traits
56+
*/
57+
export const updateMemberTraits = (handle, updatedTraits) => {
58+
return axios.put(`${TC_API_URL}/v3/members/${handle}/traits`, {
59+
param: updatedTraits
60+
})
61+
.then(resp => _.get(resp.data, 'result.content', {}))
62+
}
63+
64+
/**
65+
* Update member photo
66+
*
67+
* @param {String} handle member handle
68+
* @param {Object} data params to update photo
69+
* @param {String} data.contentType photo file content type
70+
* @param {String} data.token token provided by pre signed URL
71+
*
72+
* @returns {Promise<String>} photo URL
73+
*/
74+
export const updateMemberPhoto = (handle, data) => {
75+
return axios.put(`${TC_API_URL}/v3/members/${handle}/photo`, {
76+
param: data
77+
})
78+
.then(resp => _.get(resp.data, 'result.content', {}))
79+
}
80+
81+
/**
82+
* Get pre-signed URL for member photo
83+
*
84+
* @param {String} handle member handle
85+
* @param {File} file file to upload
86+
*
87+
* @returns {Promise<Object>} data of pre-signed URL
88+
*/
89+
export const getPreSignedUrl = (handle, file) => {
90+
return axios.post(`${TC_API_URL}/v3/members/${handle}/photoUploadUrl`, {
91+
param: {
92+
contentType: file.type
93+
}
94+
})
95+
.then(resp => _.get(resp.data, 'result.content', {}))
96+
}
97+
98+
/**
99+
* Check if email is available to be used for a user
100+
*
101+
* @param {String} email email to validate
102+
*
103+
* @returns {Promise<Object>} response body
104+
*/
105+
export const checkEmailValidity = (email) => {
106+
return axios.get(`${TC_API_URL}/v3/users/validateEmail?email=${email}`)
107+
.then(resp => _.get(resp.data, 'result.content', {}))
108+
}
109+
110+
/**
111+
* Update user password
112+
*
113+
* @param {Number} userId user id
114+
* @param {Object} credential user credentials old and new one
115+
*
116+
* @returns {Promise<Object>} response body
117+
*/
118+
export const updatePassword = (userId, credential) => {
119+
return axios.patch(`${TC_API_URL}/v3/users/${userId}`, {
120+
param: { credential }
121+
})
122+
.then(resp => _.get(resp.data, 'result.content', {}))
123+
}
124+
125+
/**
126+
* Send reset password email to the user
127+
*
128+
* @param {String} email user email
129+
*
130+
* @returns {Promise<Object>} response body
131+
*/
132+
export const resetPassword = (email) => {
133+
return axios.get(`${TC_API_URL}/v3/users/resetToken?email=${encodeURIComponent(email)}&resetPasswordUrlPrefix=${encodeURIComponent(RESET_PASSWORD_URL)}`)
134+
.then(resp => _.get(resp.data, 'result.content', {}))
135+
}

src/components/CoderBot/CoderBot.jsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react'
22
import PropTypes from 'prop-types'
33
import './CoderBot.scss'
44
import CoderBroken from '../../assets/icons/coder-broken.svg'
5+
import CoderHappy from '../../assets/icons/coder-welcome.svg'
56

67

78

@@ -26,14 +27,17 @@ const getMessage = code => {
2627
}
2728
}
2829

29-
const CoderBot = ({code, message}) => {
30+
const CoderBot = ({code, message, heading, children}) => {
3031
return (
3132
<section className="content content-error">
3233
<div className="container">
3334
<div className="page-error">
34-
<h3>{ getHeading(code) }</h3>
35-
<p dangerouslySetInnerHTML={ {__html : message || getMessage(code) } } />
36-
<CoderBroken className="icon-coder-broken" />
35+
<h3>{ heading || getHeading(code) }</h3>
36+
<div className="content">
37+
<p dangerouslySetInnerHTML={ {__html : message || getMessage(code) } } />
38+
<div>{children}</div>
39+
</div>
40+
{code !== 200 ? <CoderBroken className="icon-coder-broken" /> : <CoderHappy className="icon-coder-broken" />}
3741
<span>{code !== 200 && code}</span>
3842
</div>
3943
</div>

src/components/CoderBot/CoderBot.scss

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,6 @@
3333
}
3434
}
3535
background-size: 307px 300px;
36-
a{
37-
color: $tc-dark-blue;
38-
&:hover {
39-
text-decoration: underline;
40-
}
41-
}
4236
h3{
4337
color: $tc-gray-70;
4438
@include roboto-medium;
@@ -56,9 +50,11 @@
5650
font-size: 30px;
5751
}
5852
}
53+
.content {
54+
min-height: 120px;
55+
}
5956
p{
6057
text-align: left;
61-
min-height: 120px;
6258
padding: 0 168px;
6359
@include roboto;
6460
font-size: $tc-label-lg;
@@ -71,6 +67,13 @@
7167
@media screen and (max-width: $screen-md - 1px) {
7268
padding: 0 28px;
7369
}
70+
71+
a{
72+
color: $tc-dark-blue;
73+
&:hover {
74+
text-decoration: underline;
75+
}
76+
}
7477
}
7578
span{
7679
position: absolute;

src/components/FileBtn/FileBtn.jsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,20 @@ import './FileBtn.scss'
88

99
const FileBtn = (props) => {
1010
const fileProps = _.pick(props, 'accept', 'onChange')
11-
11+
const { disabled } = props
1212
return (
1313
<div className="file-btn">
14-
<input className="file" type="file" {...fileProps} />
15-
<button className="tc-btn tc-btn-default" tabIndex="-1">Update</button>
14+
<input className="file" type="file" {...fileProps} disabled={disabled} />
15+
<button className="tc-btn tc-btn-default" tabIndex="-1" disabled={disabled}>{props.label}</button>
1616
</div>
1717
)
1818
}
1919

2020
FileBtn.propTypes = {
21+
label: PropTypes.string.isRequired,
2122
accept: PropTypes.string,
22-
onChange: PropTypes.func
23+
onChange: PropTypes.func,
24+
disabled: PropTypes.bool
2325
}
2426

2527
export default FileBtn

src/components/FileBtn/FileBtn.scss

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,23 @@
1414
right: 0;
1515
top: 0;
1616
font-size: 100px;
17+
18+
&[disabled] {
19+
cursor: default;
20+
}
1721
}
1822

1923
/* here we reproduce styles for tc-btn-default from tc-ui package */
2024
> .file.loading:enabled + .tc-btn-default,
2125
> .file:focus:enabled + .tc-btn-default {
22-
background: $tc-white;
23-
border-color: $tc-dark-blue-70;
26+
border-color: $tc-dark-blue-100;
2427
box-shadow: 0 0 2px 1px $tc-dark-blue-30;
2528
}
2629

2730
> .file:active:enabled + .tc-btn-default,
2831
> .file:hover:enabled + .tc-btn-default {
29-
background-image: linear-gradient(0deg, $tc-gray-neutral-light 0%, $tc-white 49%, $tc-white 100%);
32+
// background-image: linear-gradient(0deg, $tc-gray-neutral-light 0%, $tc-white 49%, $tc-white 100%);
33+
border-color: $tc-dark-blue-100;
3034
border-color: $tc-gray-40;
3135
}
3236

src/components/Footer/Footer.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ const Footer = () => {
1818
const isProjectDetails = /projects\/\d+/.test(window.location.pathname)
1919
const isCreateProject = window.location.pathname.startsWith(NEW_PROJECT_PATH)
2020
const isNotificationsPage = window.location.pathname.startsWith('/notifications')
21+
const isSettingsPage = window.location.pathname.startsWith('/settings/')
2122

2223
// TODO this looks like a bad way of doing it, I think it should be re-factored
23-
const shouldHideOnDesktop = isProjectDetails || isCreateProject || isNotificationsPage
24+
const shouldHideOnDesktop = isProjectDetails || isCreateProject || isNotificationsPage || isSettingsPage
2425
// on mobile show footer only when user is logged-out, so only root page is available
2526
const shouldHideOnMobile = window.location.pathname !== '/'
2627

0 commit comments

Comments
 (0)