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: 3 additions & 1 deletion config/constants/dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,7 @@ module.exports = {
TC_NOTIFICATION_URL: 'https://api.topcoder-dev.com/v5/notifications',
CONNECT_MESSAGE_API_URL: 'https://api.topcoder-dev.com/v5',
TC_SYSTEM_USERID: process.env.DEV_TC_SYSTEM_USERID,
MAINTENANCE_MODE: process.env.DEV_MAINTENANCE_MODE
MAINTENANCE_MODE: process.env.DEV_MAINTENANCE_MODE,

RESET_PASSWORD_URL: 'https://accounts.topcoder-dev.com/connect/reset-password'
}
4 changes: 3 additions & 1 deletion config/constants/master.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,7 @@ module.exports = {
TC_NOTIFICATION_URL: 'https://api.topcoder.com/v5/notifications',
CONNECT_MESSAGE_API_URL: 'https://api.topcoder.com/v5',
TC_SYSTEM_USERID: process.env.PROD_TC_SYSTEM_USERID,
MAINTENANCE_MODE: process.env.PROD_MAINTENANCE_MODE
MAINTENANCE_MODE: process.env.PROD_MAINTENANCE_MODE,

RESET_PASSWORD_URL: 'https://accounts.topcoder.com/connect/reset-password'
}
2 changes: 0 additions & 2 deletions src/actions/loadUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ export function loadUserSuccess(dispatch, token) {
if (currentUser) {
getUserProfile(currentUser.handle).then((profile) => {
currentUser = _.assign(currentUser, profile)
// keeping profile for backward compatibility
currentUser.profile = profile
// determine user role
let userRole
if (_.indexOf(currentUser.roles, ROLE_ADMINISTRATOR) > -1) {
Expand Down
35 changes: 35 additions & 0 deletions src/api/s3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Amazon S3 Service API
*/

/**
* Upload file to S3 using pre-signed URL
*
* @param {String} preSignedURL pre-signed URL
* @param {File} file file to upload
*
* @returns {Promise<String>} pre-signed URL
*/
export const uploadFileToS3 = (preSignedURL, file) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()

xhr.open('PUT', preSignedURL, true)
xhr.setRequestHeader('Content-Type', file.type)

xhr.onreadystatechange = () => {
const { status } = xhr
if (((status >= 200 && status < 300) || status === 304) && xhr.readyState === 4) {
resolve(preSignedURL)
} else if (status >= 400) {
const err = new Error('Could not upload image')
err.status = status
reject(err)
}
}
xhr.onerror = (err) => {
reject(err)
}
xhr.send(file)
})
}
126 changes: 123 additions & 3 deletions src/api/users.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,135 @@
import _ from 'lodash'
import { axiosInstance as axios } from './requestInterceptor'
import { TC_API_URL } from '../config/constants'
import { RESET_PASSWORD_URL } from '../../config/constants'

/**
* Get a user basd on it's handle/username
* @param {integer} handle unique identifier of the user
* @return {object} user returned by api
* Get a user based on it's handle/username
*
* @param {String} handle user handle
*
* @returns {Promise<Object>} user profile data
*/
export function getUserProfile(handle) {
return axios.get(`${TC_API_URL}/v3/members/${handle}/`)
.then(resp => {
return _.get(resp.data, 'result.content', {})
})
}

/**
* Update user profile
*
* @param {String} handle user handle
* @param {Object} updatedProfile updated user data
*
* @returns {Promise<Object>} user profile data
*/
export function updateUserProfile(handle, updatedProfile) {
return axios.put(`${TC_API_URL}/v3/members/${handle}/`, {
param: updatedProfile
})
.then(resp => {
return _.get(resp.data, 'result.content', {})
})
}

/**
* Get member traits
*
* @param {String} handle member handle
*
* @returns {Promise<Array>} member traits
*/
export const getMemberTraits = (handle) => {
return axios.get(`${TC_API_URL}/v3/members/${handle}/traits`)
.then(resp => _.get(resp.data, 'result.content', {}))
}

/**
* Update member traits
*
* @param {String} handle member handle
* @param {Array} updatedTraits list of updated traits
*
* @returns {Promise<Array>} member traits
*/
export const updateMemberTraits = (handle, updatedTraits) => {
return axios.put(`${TC_API_URL}/v3/members/${handle}/traits`, {
param: updatedTraits
})
.then(resp => _.get(resp.data, 'result.content', {}))
}

/**
* Update member photo
*
* @param {String} handle member handle
* @param {Object} data params to update photo
* @param {String} data.contentType photo file content type
* @param {String} data.token token provided by pre signed URL
*
* @returns {Promise<String>} photo URL
*/
export const updateMemberPhoto = (handle, data) => {
return axios.put(`${TC_API_URL}/v3/members/${handle}/photo`, {
param: data
})
.then(resp => _.get(resp.data, 'result.content', {}))
}

/**
* Get pre-signed URL for member photo
*
* @param {String} handle member handle
* @param {File} file file to upload
*
* @returns {Promise<Object>} data of pre-signed URL
*/
export const getPreSignedUrl = (handle, file) => {
return axios.post(`${TC_API_URL}/v3/members/${handle}/photoUploadUrl`, {
param: {
contentType: file.type
}
})
.then(resp => _.get(resp.data, 'result.content', {}))
}

/**
* Check if email is available to be used for a user
*
* @param {String} email email to validate
*
* @returns {Promise<Object>} response body
*/
export const checkEmailValidity = (email) => {
return axios.get(`${TC_API_URL}/v3/users/validateEmail?email=${email}`)
.then(resp => _.get(resp.data, 'result.content', {}))
}

/**
* Update user password
*
* @param {Number} userId user id
* @param {Object} credential user credentials old and new one
*
* @returns {Promise<Object>} response body
*/
export const updatePassword = (userId, credential) => {
return axios.patch(`${TC_API_URL}/v3/users/${userId}`, {
param: { credential }
})
.then(resp => _.get(resp.data, 'result.content', {}))
}

/**
* Send reset password email to the user
*
* @param {String} email user email
*
* @returns {Promise<Object>} response body
*/
export const resetPassword = (email) => {
return axios.get(`${TC_API_URL}/v3/users/resetToken?email=${encodeURIComponent(email)}&resetPasswordUrlPrefix=${encodeURIComponent(RESET_PASSWORD_URL)}`)
.then(resp => _.get(resp.data, 'result.content', {}))
}
12 changes: 8 additions & 4 deletions src/components/CoderBot/CoderBot.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import './CoderBot.scss'
import CoderBroken from '../../assets/icons/coder-broken.svg'
import CoderHappy from '../../assets/icons/coder-welcome.svg'



Expand All @@ -26,14 +27,17 @@ const getMessage = code => {
}
}

const CoderBot = ({code, message}) => {
const CoderBot = ({code, message, heading, children}) => {
return (
<section className="content content-error">
<div className="container">
<div className="page-error">
<h3>{ getHeading(code) }</h3>
<p dangerouslySetInnerHTML={ {__html : message || getMessage(code) } } />
<CoderBroken className="icon-coder-broken" />
<h3>{ heading || getHeading(code) }</h3>
<div className="content">
<p dangerouslySetInnerHTML={ {__html : message || getMessage(code) } } />
<div>{children}</div>
</div>
{code !== 200 ? <CoderBroken className="icon-coder-broken" /> : <CoderHappy className="icon-coder-broken" />}
<span>{code !== 200 && code}</span>
</div>
</div>
Expand Down
17 changes: 10 additions & 7 deletions src/components/CoderBot/CoderBot.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@
}
}
background-size: 307px 300px;
a{
color: $tc-dark-blue;
&:hover {
text-decoration: underline;
}
}
h3{
color: $tc-gray-70;
@include roboto-medium;
Expand All @@ -56,9 +50,11 @@
font-size: 30px;
}
}
.content {
min-height: 120px;
}
p{
text-align: left;
min-height: 120px;
padding: 0 168px;
@include roboto;
font-size: $tc-label-lg;
Expand All @@ -71,6 +67,13 @@
@media screen and (max-width: $screen-md - 1px) {
padding: 0 28px;
}

a{
color: $tc-dark-blue;
&:hover {
text-decoration: underline;
}
}
}
span{
position: absolute;
Expand Down
10 changes: 6 additions & 4 deletions src/components/FileBtn/FileBtn.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@ import './FileBtn.scss'

const FileBtn = (props) => {
const fileProps = _.pick(props, 'accept', 'onChange')

const { disabled } = props
return (
<div className="file-btn">
<input className="file" type="file" {...fileProps} />
<button className="tc-btn tc-btn-default" tabIndex="-1">Update</button>
<input className="file" type="file" {...fileProps} disabled={disabled} />
<button className="tc-btn tc-btn-default" tabIndex="-1" disabled={disabled}>{props.label}</button>
</div>
)
}

FileBtn.propTypes = {
label: PropTypes.string.isRequired,
accept: PropTypes.string,
onChange: PropTypes.func
onChange: PropTypes.func,
disabled: PropTypes.bool
}

export default FileBtn
10 changes: 7 additions & 3 deletions src/components/FileBtn/FileBtn.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,23 @@
right: 0;
top: 0;
font-size: 100px;

&[disabled] {
cursor: default;
}
}

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

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

Expand Down
3 changes: 2 additions & 1 deletion src/components/Footer/Footer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ const Footer = () => {
const isProjectDetails = /projects\/\d+/.test(window.location.pathname)
const isCreateProject = window.location.pathname.startsWith(NEW_PROJECT_PATH)
const isNotificationsPage = window.location.pathname.startsWith('/notifications')
const isSettingsPage = window.location.pathname.startsWith('/settings/')

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

Expand Down
1 change: 1 addition & 0 deletions src/components/TopBar/ProjectsToolBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ class ProjectsToolBar extends Component {
const { user, criteria, creatingProject, projectCreationError, searchTermTag, projectTypes } = this.props
const { errorCreatingProject, isFilterVisible, isMobileMenuOpen, isMobileSearchVisible } = this.state
return (nextProps.user || {}).handle !== (user || {}).handle
|| (nextProps.user || {}).photoURL !== (this.props.user || {}).photoURL
|| JSON.stringify(nextProps.criteria) !== JSON.stringify(criteria)
|| nextProps.creatingProject !== creatingProject
|| nextProps.projectCreationError !== projectCreationError
Expand Down
9 changes: 4 additions & 5 deletions src/components/TopBar/SectionToolBar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
:global {
.tc-header.tc-header__connect .top-bar{
height: 60px;
background-color: $tc-gray-90;
background-color: $tc-gray-100;
position: relative;
display: flex;

Expand All @@ -30,7 +30,7 @@
}

.title {
@include roboto-light;
@include roboto;
font-size: 20px;
color: $tc-gray-10;
text-align: center;
Expand Down Expand Up @@ -65,11 +65,11 @@
}

.icon-x-mark {
fill: $tc-white;
fill: $tc-gray-100;
margin: -6px;

@media screen and (max-width: $screen-md - 1px) {
fill: $tc-white;
fill: $tc-gray-100;
}
}
}
Expand Down Expand Up @@ -102,7 +102,6 @@
.icon-connect-logo-mono {
height: auto;
width: 53px;
margin-top: 13px;

path {
fill: $tc-gray-10;
Expand Down
Loading