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 @@ -54,5 +54,7 @@ module.exports = {

DASHBOARD_FAQ_CONTENT_ID : process.env.DASHBOARD_FAQ_CONTENT_ID,
CONTENTFUL_DELIVERY_KEY : process.env.CONTENTFUL_DELIVERY_KEY,
CONTENTFUL_SPACE_ID : process.env.CONTENTFUL_SPACE_ID
CONTENTFUL_SPACE_ID : process.env.CONTENTFUL_SPACE_ID,

DEFAULT_NDA_UUID: 'e5811a7b-43d1-407a-a064-69e5015b4900'
}
4 changes: 3 additions & 1 deletion config/constants/master.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,7 @@ module.exports = {

DASHBOARD_FAQ_CONTENT_ID : process.env.DASHBOARD_FAQ_CONTENT_ID,
CONTENTFUL_DELIVERY_KEY : process.env.CONTENTFUL_DELIVERY_KEY,
CONTENTFUL_SPACE_ID : process.env.CONTENTFUL_SPACE_ID
CONTENTFUL_SPACE_ID : process.env.CONTENTFUL_SPACE_ID,

DEFAULT_NDA_UUID: 'c41e90e5-4d0e-4811-bd09-38ff72674490'
}
4 changes: 3 additions & 1 deletion config/constants/qa.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,7 @@ module.exports = {
TC_SYSTEM_USERID: process.env.QA_TC_SYSTEM_USERID,
MAINTENANCE_MODE: process.env.QA_MAINTENANCE_MODE,

TC_CDN_URL: process.env.TC_CDN_URL
TC_CDN_URL: process.env.TC_CDN_URL,

DEFAULT_NDA_UUID: 'e5811a7b-43d1-407a-a064-69e5015b4900'
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/config/permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,18 @@ export const PERMISSIONS = {
...TOPCODER_ADMINS,
]
},

VIEW_PROJECT_DEFAULTS: {
meta: {
},
projectRoles: [
..._.difference(PROJECT_ALL, PROJECT_ROLE_CUSTOMER)
],
topcoderRoles: [
...TOPCODER_ADMINS,
..._.difference(TOPCODER_ALL, ROLE_TOPCODER_USER)
]
}
}

/**
Expand Down
10 changes: 10 additions & 0 deletions src/helpers/projectHelper.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import _ from 'lodash'
import moment from 'moment'
import { findProduct } from '../config/projectWizard'
import { PERMISSIONS } from 'config/permissions'
import { hasPermission } from 'helpers/permissions'

import {
PHASE_STATUS_ACTIVE,
Expand All @@ -17,6 +19,7 @@ import MessagesIcon from '../assets/icons/v.2.5/icon-messages.svg'
import ReportsIcon from '../assets/icons/v.2.5/icon-reports.svg'
import AssetsLibraryIcon from '../assets/icons/v.2.5/icon-assets-library.svg'
import FAQIcon from '../assets/icons/faq.svg'
import AccountSecurityIcon from 'assets/icons/v.2.5/icon-account-security.svg'
import InvisibleIcon from '../assets/icons/invisible.svg'

import { formatNumberWithCommas } from './format'
Expand Down Expand Up @@ -278,5 +281,12 @@ export function getProjectNavLinks(project, projectId, renderFAQs) {
const faqTab = { label: 'FAQ', to: `/projects/${projectId}/faqs`, Icon: FAQIcon, iconClassName: 'fill' }
navLinks.push(faqTab)
}

const searchParams = new URLSearchParams(window.location.search)

if (searchParams.get('beta') === 'true' && hasPermission(PERMISSIONS.VIEW_PROJECT_DEFAULTS)) {
navLinks.push({ label: 'Project Defaults', to: `/projects/${projectId}/projectDefaults`, Icon: AccountSecurityIcon, iconClassName: 'stroke' })
}

return navLinks
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React from 'react'
import {connect} from 'react-redux'
import FormsyForm from 'appirio-tech-react-components/components/Formsy'
const Formsy = FormsyForm.Formsy
import RadioGroup from 'appirio-tech-react-components/components/Formsy/RadioGroup'
import SpecQuestionList from '../SpecQuestionList/SpecQuestionList'
import Accordion from '../Accordion/Accordion'
import { updateProject } from '../../../actions/project'
import { DEFAULT_NDA_UUID } from '../../../../../config/constants'

import './EditProjectDefaultsForm.scss'

class EditProjectDefaultsForm extends React.Component {
constructor(props) {
super(props)

this.state = {
hasNda: false,
enableButton: false,
isLoading: true
}

this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}

componentDidMount() {
const {terms} = this.props.project
if (terms.indexOf(DEFAULT_NDA_UUID) >= 0) {
this.setState({hasNda: true})
}
this.setState({isLoading: false})
}

handleChange({nda}) {
if ((nda === 'yes') !== this.state.hasNda) {
if (this.state.enableButton !== true) {
this.setState({enableButton: true})
}
} else {
if (this.state.enableButton !== false) {
this.setState({enableButton: false})
}
}
}

async handleSubmit() {
const {updateProject} = this.props
const {id, terms} = this.props.project
const newHasNda = !this.state.hasNda
if (newHasNda) {
await updateProject(id, {
terms: [...new Set([...terms, DEFAULT_NDA_UUID])]
}, true)
} else {
const newTerms = [...terms]
if (newTerms.indexOf(DEFAULT_NDA_UUID) >= 0) {
newTerms.splice(newTerms.indexOf(DEFAULT_NDA_UUID), 1)
await updateProject(id, {
terms: newTerms
}, true)
}
}
this.setState({
hasNda: this.props.project.terms.indexOf(DEFAULT_NDA_UUID) >= 0
})
}

render() {
const opts = [
{
value: 'yes',
label: 'Yes'
},
{
value: 'no',
label: 'No'
}
]

if (this.state.isLoading) return null;

return (
<div className="edit-project-defaults-form">
<Formsy.Form
onValidSubmit={this.handleSubmit}
onChange={this.handleChange}
>
<div className="container">
<SpecQuestionList>
<Accordion
title="Enforce Topcoder NDA"
type="radio-group"
options={opts}
>
<SpecQuestionList.Item>
<RadioGroup
name="nda"
options={opts}
value={this.state.hasNda ? 'yes' : 'no'}
/>
</SpecQuestionList.Item>
</Accordion>
</SpecQuestionList>
</div>
<div className="section-footer section-footer-spec">
<button
className="tc-btn tc-btn-primary tc-btn-md"
type="submit"
disabled={!this.state.enableButton}
>
Submit
</button>
</div>
</Formsy.Form>
</div>
)
}
}

const mapDispatchToProps = {
updateProject
}

export default connect(null, mapDispatchToProps)(EditProjectDefaultsForm)
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
@import '~tc-ui/src/styles/tc-includes';

:global {
.edit-project-defaults-form {
background-color: white;
.container {
padding: 50px 0 70px 0;
margin: 0 50px 20px 50px;
}
.radio-group-options {
flex-direction: column;
}

.radio label {
font-size: 15px;
font-weight: 400;
}

.radio-group-options .radio {
margin: 0 0 10px 0;
background-color: $tc-gray-neutral-light;
padding: 10px;
border-radius: 4px;
display: block;

&:first-child {
margin-top: 10px;
}
}

.radio-group-options .radio.selected {
background-color: $tc-dark-blue-10;
}

.radio-group-options .radio-option-price {
float: right;
font-size: 15px;
color: $tc-gray-100;
}

.radio-option-description {
color: $tc-gray-70;
font-size: 13px;
margin-top: 10px;
line-height: 20px;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import EditProjectDefaults from './EditProjectDefaultsForm'
export default EditProjectDefaults
84 changes: 84 additions & 0 deletions src/projects/detail/containers/ProjectDefaultsContainer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, { Component } from 'react'
import MediaQuery from 'react-responsive'
import Sticky from 'react-stickynode'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { SCREEN_BREAKPOINT_MD, PROJECT_FEED_TYPE_PRIMARY, PROJECT_FEED_TYPE_MESSAGES } from '../../../config/constants'
import TwoColsLayout from '../../../components/TwoColsLayout'
import ProjectInfoContainer from './ProjectInfoContainer'
import EditProjectDefaultsForm from '../components/EditProjectDefaultsForm'
import { hasPermission } from '../../../helpers/permissions'
import { PERMISSIONS } from '../../../config/permissions'

class ProjectDefaultsContainer extends Component {
render() {
const {
project,
phases,
isProcessing,
feeds,
isFeedsLoading,
productsTimelines,
phasesTopics,
location,
} = this.props

const leftArea = (
<ProjectInfoContainer
location={location}
project={project}
phases={phases}
feeds={feeds}
isFeedsLoading={isFeedsLoading}
productsTimelines={productsTimelines}
phasesTopics={phasesTopics}
isProjectProcessing={isProcessing}
/>
)

return (
<TwoColsLayout>
<TwoColsLayout.Sidebar>
<MediaQuery minWidth={SCREEN_BREAKPOINT_MD}>
{(matches) => {
if (matches) {
return (
<Sticky top={60} bottomBoundary="#wrapper-main">
{leftArea}
</Sticky>
)
} else {
return leftArea
}
}}
</MediaQuery>
</TwoColsLayout.Sidebar>
<TwoColsLayout.Content>
<EditProjectDefaultsForm project={this.props.project} />
</TwoColsLayout.Content>
</TwoColsLayout>
)
}
}
const mapStateToProps = ({ loadUser, projectState, projectTopics, topics }) => {
let allFeed = projectTopics.feeds[PROJECT_FEED_TYPE_PRIMARY].topics
if (hasPermission(PERMISSIONS.ACCESS_PRIVATE_POST)) {
allFeed = [...allFeed, ...projectTopics.feeds[PROJECT_FEED_TYPE_MESSAGES].topics]
}

return {
user: loadUser.user,
isProcessing: projectState.processing,
phases: projectState.phases,
phasesNonDirty: projectState.phasesNonDirty,
isLoadingPhases: projectState.isLoadingPhases,
feeds: allFeed,
isFeedsLoading: projectTopics.isLoading,
phasesTopics: topics,
}
}

const mapDispatchToProps = {
}

export default connect(mapStateToProps, mapDispatchToProps)(withRouter(ProjectDefaultsContainer))
11 changes: 10 additions & 1 deletion src/projects/detail/containers/SecondaryToolBarContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import React from 'react'
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
import PT from 'prop-types'
import { PERMISSIONS } from 'config/permissions'
import { hasPermission } from 'helpers/permissions'

import GenericMenu from 'components/GenericMenu'

Expand All @@ -24,7 +26,7 @@ const SecondaryToolBarContainer = ({
{ label: 'Messages', to: `/projects/${match.params.projectId}/messages` },
{ label: 'Scope', to: `/projects/${match.params.projectId}/scope` },
{ label: 'Project Plan', to: `/projects/${match.params.projectId}/plan` },
{ label: 'Assets Library', to: `/projects/${match.params.projectId}/assets` },
{ label: 'Assets Library', to: `/projects/${match.params.projectId}/assets` }
] : [
{ label: 'Dashboard', to: `/projects/${match.params.projectId}` },
{ label: 'Specification', to: `/projects/${match.params.projectId}/specification` },
Expand All @@ -36,6 +38,13 @@ const SecondaryToolBarContainer = ({
navLinks.push({ label: 'Discussions', to: `/projects/${match.params.projectId}/discussions` })
}


const searchParams = new URLSearchParams(window.location.search)

if (searchParams.get('beta') === 'true' && hasPermission(PERMISSIONS.VIEW_PROJECT_DEFAULTS) && project.version === 'v3') {
navLinks.push({ label: 'Project Defaults', to: `/projects/${match.params.projectId}/projectDefaults` })
}

return (
<GenericMenu navLinks={navLinks} />
)
Expand Down
2 changes: 2 additions & 0 deletions src/projects/routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import CoderBot from '../components/CoderBot/CoderBot'
import SpecificationContainer from './detail/containers/SpecificationContainer'
import { requiresAuthentication } from '../components/AuthenticatedComponent'
import ProjectFAQContainer from './detail/containers/ProjectFAQContainer'
import ProjectDefaultsContainer from './detail/containers/ProjectDefaultsContainer'

const FileDownloadWithAuth = requiresAuthentication(FileDownload)

Expand Down Expand Up @@ -58,6 +59,7 @@ const ProjectDetailWithAuth = requiresAuthentication(() =>
<Route path="/projects/:projectId/assets" render={() => <ProjectDetail component={AssetsLibrary} />} />
<Route path="/projects/:projectId/discussions/:discussionId?" render={() => <ProjectDetail component={ProjectMessages} />} />
<Route path="/projects/:projectId/faqs" render={() => <ProjectDetail component={ProjectFAQContainer} />} />
<Route path="/projects/:projectId/projectDefaults" render={() => <ProjectDetail component={ProjectDefaultsContainer} />} />
<Route render={() => <CoderBot code={404}/>} />
</Switch>)
)
Expand Down