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
79 changes: 6 additions & 73 deletions src/components/TopBar/ProjectsToolBar.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
require('./ProjectsToolBar.scss')

import React, {PropTypes, Component} from 'react'
import { Link } from 'react-router'
import { connect } from 'react-redux'
import cn from 'classnames'
import _ from 'lodash'
import Modal from 'react-modal'
import { SearchBar } from 'appirio-tech-react-components'
import Filters from './Filters'

import ModalControl from '../ModalControl'
import SVGIconImage from '../SVGIconImage'
import CoderBot from '../CoderBot/CoderBot'
import ProjectWizard from '../../projects/create/components/ProjectWizard'

import { createProjectWithStatus as createProjectAction } from '../../projects/actions/project'
import { projectSuggestions, loadProjects } from '../../projects/actions/loadProjects'
import {
ROLE_CONNECT_COPILOT,
ROLE_CONNECT_MANAGER,
ROLE_ADMINISTRATOR,
PROJECT_STATUS_IN_REVIEW
ROLE_ADMINISTRATOR
} from '../../config/constants'


Expand All @@ -28,18 +21,14 @@ class ProjectsToolBar extends Component {
constructor(props) {
super(props)
this.state = {
isCreateProjectModalVisible : false,
errorCreatingProject: false,
isFilterVisible: false
}
this.applyFilters = this.applyFilters.bind(this)
this.toggleFilter = this.toggleFilter.bind(this)
this.showCreateProjectDialog = this.showCreateProjectDialog.bind(this)
this.hideCreateProjectDialog = this.hideCreateProjectDialog.bind(this)
this.handleTermChange = this.handleTermChange.bind(this)
this.handleSearch = this.handleSearch.bind(this)
this.handleMyProjectsFilter = this.handleMyProjectsFilter.bind(this)
this.createProject = this.createProject.bind(this)
this.onLeave = this.onLeave.bind(this)
}

Expand All @@ -51,7 +40,6 @@ class ProjectsToolBar extends Component {
this.setState({
isProjectDirty : false
}, () => {
this.hideCreateProjectDialog()
this.props.router.push('/projects/' + nextProps.project.id)
})
} else {
Expand Down Expand Up @@ -103,26 +91,6 @@ class ProjectsToolBar extends Component {
this.applyFilters({memberOnly: event.target.checked})
}

showCreateProjectDialog() {
this.setState({
isCreateProjectModalVisible : true
})
}

hideCreateProjectDialog() {
let confirm = true
if (this.state.isProjectDirty) {
confirm = window.confirm('You have unsaved changes. Are you sure you want to leave?')
}
if (confirm === true) {
this.setState({
isProjectDirty: false,
isCreateProjectModalVisible : false,
errorCreatingProject: false
})
}
}

applyFilters(filter) {
const criteria = _.assign({}, this.props.criteria, filter)
if (criteria && criteria.keyword) {
Expand Down Expand Up @@ -155,62 +123,27 @@ class ProjectsToolBar extends Component {
this.props.loadProjects(criteria, page)
}

createProject(project) {
this.props.createProjectAction(project, PROJECT_STATUS_IN_REVIEW)
}

shouldComponentUpdate(nextProps, nextState) {
const { user, criteria, creatingProject, projectCreationError, searchTermTag } = this.props
const { isCreateProjectModalVisible, errorCreatingProject, isFilterVisible } = this.state
const { errorCreatingProject, isFilterVisible } = this.state
return nextProps.user.handle !== user.handle
|| JSON.stringify(nextProps.criteria) !== JSON.stringify(criteria)
|| nextProps.creatingProject !== creatingProject
|| nextProps.projectCreationError !== projectCreationError
|| nextProps.searchTermTag !== searchTermTag
|| nextState.isCreateProjectModalVisible !== isCreateProjectModalVisible
|| nextState.errorCreatingProject !== errorCreatingProject
|| nextState.isFilterVisible !== isFilterVisible
}

render() {
const { logo, userMenu, userRoles, criteria, isPowerUser } = this.props
const { isCreateProjectModalVisible, errorCreatingProject, isFilterVisible } = this.state
const { isFilterVisible } = this.state
const isLoggedIn = userRoles && userRoles.length

const noOfFilters = _.keys(criteria).length - 1 // -1 for default sort criteria

return (
<div className="ProjectsToolBar">
<Modal
isOpen={ isCreateProjectModalVisible }
className="project-creation-dialog"
overlayClassName="project-creation-dialog-overlay"
onRequestClose={ this.hideCreateProjectDialog }
contentLabel=""
>
<ModalControl
className="escape-button"
icon={<SVGIconImage filePath="x-mark" />}
label="esc"
onClick={ this.hideCreateProjectDialog }
/>
{ !errorCreatingProject &&
<ProjectWizard
showModal={ false }
processing={ this.props.creatingProject }
createProject={ this.createProject }
closeModal={ this.hideCreateProjectDialog }
userRoles={ userRoles }
onProjectUpdate={ (updatedProject, dirty=true) => {
this.setState({
isProjectDirty: dirty
})
}
}
/>
}
{ errorCreatingProject && <CoderBot code={ 500 } message="Unable to create project" />}
</Modal>
<div className="primary-toolbar">
{ logo }
{
Expand Down Expand Up @@ -240,7 +173,7 @@ class ProjectsToolBar extends Component {
{
!!isLoggedIn &&
<div>
<a onClick={ this.showCreateProjectDialog } href="javascript:" className="tc-btn tc-btn-sm tc-btn-primary">+ New Project</a>
<Link to="/new-project" className="tc-btn tc-btn-sm tc-btn-primary">+ New Project</Link>
</div>
}
{ userMenu }
Expand Down Expand Up @@ -289,6 +222,6 @@ const mapStateToProps = ({ projectSearchSuggestions, searchTerm, projectSearch,
}
}

const actionsToBind = { projectSuggestions, loadProjects, createProjectAction }
const actionsToBind = { projectSuggestions, loadProjects }

export default connect(mapStateToProps, actionsToBind)(ProjectsToolBar)
2 changes: 1 addition & 1 deletion src/config/projectWizard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ const products = {
details: 'Translate designs to a web (HTML/CSS/JavaScript) or mobile prototype',
icon: 'product-dev-prototype',
id: 'visual_prototype',
aliases: ['visual-prototype','visual_prototype'],
aliases: ['visual-prototype', 'visual_prototype'],
disabled: true
},
'Front-end': {
Expand Down
46 changes: 29 additions & 17 deletions src/projects/create/components/ProjectWizard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class ProjectWizard extends Component {
const incompleteProject = JSON.parse(incompleteProjectStr)
const incompleteProduct = _.get(incompleteProject, 'details.products[0]')
let wizardStep = WZ_STEP_INCOMP_PROJ_CONF
let updateQuery = {}
if (incompleteProduct && params && params.product) {
// assumes the params.product to be id of a product because incomplete project is set only
// after user selects a particular product
Expand All @@ -59,20 +60,21 @@ class ProjectWizard extends Component {
// load project detais page directly if product of save project and deep link are the same
if (product.id === incompleteProduct) {
wizardStep = WZ_STEP_FILL_PROJ_DETAILS
} else { // for different product type, update local state with URL params
const projectType = findProductCategory(params.product)
_.set(incompleteProject, 'type', projectType.id)
_.set(incompleteProject, 'details.products[0]', product.id)
updateQuery = {$merge : incompleteProject}
} else {
// explicitly ignores the wizardStep returned by the method
// we need to call this method just to get updateQuery updated with correct project type and product
this.loadProjectAndProductFromURL(params, updateQuery)
}
}
}
this.setState({
project: update(this.state.project, {$merge : incompleteProject}),
dirtyProject: update(this.state.dirtyProject, {$merge : incompleteProject}),
wizardStep: wizardStep,
project: update(this.state.project, updateQuery),
dirtyProject: update(this.state.dirtyProject, updateQuery),
wizardStep,
isProjectDirty: false
}, () => {
typeof onStepChange === 'function' && onStepChange(this.state.wizardStep)
typeof onStepChange === 'function' && onStepChange(this.state.wizardStep, this.state.project)
})
} else {
// if there is no incomplete project in the local storage, load the wizard with appropriate step
Expand Down Expand Up @@ -111,7 +113,7 @@ class ProjectWizard extends Component {
let wizardStep = type && product ? WZ_STEP_FILL_PROJ_DETAILS : null
const updateQuery = {}
if (params && params.product) { // if there exists product path param
wizardStep = this.loadProjectAndProductFromURL(params.product, updateQuery)
wizardStep = this.loadProjectAndProductFromURL(params, updateQuery)
} else { // if there is not product path param, it should be first step of the wizard
updateQuery['type'] = { $set : null }
updateQuery['details'] = { products : { $set: [] } }
Expand All @@ -132,22 +134,28 @@ class ProjectWizard extends Component {
/**
* Loads project type and product from the given URL parameter.
*
* @param {string} urlParam URL parameter value which represents either project type or product
* @param {object} urlParams URL parameters map
* @param {object} updateQuery query object which would be updated according to parsed project type and product
*
* @return {number} step where wizard should move after parsing the URL param
*/
loadProjectAndProductFromURL(urlParam, updateQuery) {
loadProjectAndProductFromURL(urlParams, updateQuery) {
const productParam = urlParams && urlParams.product
const statusParam = urlParams && urlParams.status
if ('incomplete' === statusParam) {
return WZ_STEP_INCOMP_PROJ_CONF
}
if (!productParam) return
// first try the path param to be a project category
let projectType = findCategory(urlParam, true)
let projectType = findCategory(productParam, true)
if (projectType) {// if its a category
updateQuery['type'] = { $set : projectType.id }
return WZ_STEP_SELECT_PROD_TYPE
} else {
// if it is not a category, it should be a product and we should be able to find a category for it
projectType = findProductCategory(urlParam, true)
projectType = findProductCategory(productParam, true)
// finds product object from product alias
const product = findProduct(urlParam, true)
const product = findProduct(productParam, true)
if (projectType) {// we can have `incomplete` as params.product
updateQuery['type'] = { $set : projectType.id }
updateQuery['details'] = { products : { $set: [product.id] } }
Expand Down Expand Up @@ -180,21 +188,25 @@ class ProjectWizard extends Component {
* Removed incomplete project from the local storage and resets the state. Also, moves wizard to the first step.
*/
removeIncompleteProject() {
const { onStepChange, onProjectUpdate } = this.props
const { onStepChange } = this.props
// remove incomplete project from local storage
window.localStorage.removeItem(LS_INCOMPLETE_PROJECT)
const projectType = _.get(this.state.project, 'type')
const product = _.get(this.state.project, 'details.products[0]')
let wizardStep = WZ_STEP_SELECT_PROJ_TYPE
let project = null
if (product) {
project = { type: projectType, details: { products: [product] } }
wizardStep = WZ_STEP_FILL_PROJ_DETAILS
} else if (projectType) {
project = { type: projectType, details: { products: [] } }
wizardStep = WZ_STEP_SELECT_PROD_TYPE
}
this.setState({
wizardStep: wizardStep
project: _.merge({}, project),
dirtyProject: _.merge({}, project),
wizardStep
}, () => {
typeof onProjectUpdate === 'function' && onProjectUpdate(this.state.dirtyProject, true)
typeof onStepChange === 'function' && onStepChange(this.state.wizardStep, this.state.project)
})
}
Expand Down
19 changes: 18 additions & 1 deletion src/projects/create/containers/CreateContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class CreateConainer extends React.Component {
}
this.createProject = this.createProject.bind(this)
this.onLeave = this.onLeave.bind(this)
this.closeWizard = this.closeWizard.bind(this)
}

componentWillReceiveProps(nextProps) {
Expand Down Expand Up @@ -139,12 +140,26 @@ class CreateConainer extends React.Component {
})
}

closeWizard() {
const { userRoles } = this.props
const isLoggedIn = userRoles && userRoles.length > 0
// calls leave handler
this.onLeave()
if (isLoggedIn) {
this.props.router.push('/projects')
} else {
this.props.router.push('/')
}
}

render() {
return (
<EnhancedCreateView
{...this.props}
createProject={ this.createProject }
processing={ this.state.creatingProject }
showModal
closeModal={ this.closeWizard }
onStepChange={ (wizardStep, updatedProject) => {
// type of the project
let projectType = _.get(updatedProject, 'type', null)
Expand All @@ -159,7 +174,9 @@ class CreateConainer extends React.Component {
// updates the productType variable to use first alias to create SEO friendly URL
productType = _.get(product, 'aliases[0]', productType)
if (wizardStep === ProjectWizard.Steps.WZ_STEP_INCOMP_PROJ_CONF) {
browserHistory.push(NEW_PROJECT_PATH + '/incomplete')
let productUrl = productType ? ('/' + productType) : ''
productUrl = !productType && projectType ? ('/' + projectType) : productUrl
browserHistory.push(NEW_PROJECT_PATH + productUrl + '/incomplete')
}
if (wizardStep === ProjectWizard.Steps.WZ_STEP_SELECT_PROJ_TYPE) {
browserHistory.push(NEW_PROJECT_PATH + '/' + window.location.search)
Expand Down
2 changes: 1 addition & 1 deletion src/routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const renderTopBarWithProjectsToolBar = (props) => <TopBarContainer toolbar={ Pr
export default (
<Route path="/" onUpdate={() => window.scrollTo(0, 0)} component={ App } onEnter={ redirectToConnect }>
<IndexRoute components={{ topbar: renderTopBarWithProjectsToolBar, content: Home }} />
<Route path="/new-project(/:product)" components={{ topbar: null, content: CreateContainer }} onEnter={ validateCreateProjectParams } />
<Route path="/new-project(/:product)(/:status)" components={{ topbar: null, content: CreateContainer }} onEnter={ validateCreateProjectParams } />
<Route path="/new-project-callback" components={{ topbar: null, content: CreateContainer }} />
<Route path="/terms" components={{ topbar: renderTopBarWithProjectsToolBar, content: ConnectTerms }} />
<Route path="/login" components={{ topbar: renderTopBarWithProjectsToolBar, content: LoginRedirect }} />
Expand Down