diff --git a/README.md b/README.md index 5b3922f8a..a96082f2d 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Install dependencies by running the following in the root of the project: - **Note:** You must use npm 3. Type `npm -v` to ensure you have a 3.x version. ## NPM Commands -- To run locally, run `npm start` and head to `http://localhost:3000/search/challenges` +- To run locally, run `npm start` and head to `http://localhost:3000/new_project` - Run tests with `npm test` or use `npm run test:watch` to rerun tests after files change - To make sure your code passes linting: `npm run lint` - To create the build: `npm run build` diff --git a/circle.yml b/circle.yml index 726aa82bf..8b7c6d9be 100644 --- a/circle.yml +++ b/circle.yml @@ -19,7 +19,7 @@ compile: deployment: development: - branch: dev + branch: [dev] owner: appirio-tech commands: - ./deploy.sh DEV diff --git a/package.json b/package.json index 2b1aa08af..6c6579336 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "react": "^15.6.1", "react-addons-css-transition-group": "^15.6.0", "react-addons-pure-render-mixin": "^15.6.0", + "query-string": "^4.3.4", "react-color": "^2.13.1", "react-datetime": "2.7.1", "react-dom": "^15.6.1", diff --git a/src/assets/images/product-chatbot.svg b/src/assets/images/product-chatbot.svg new file mode 100644 index 000000000..05b1ea7b0 --- /dev/null +++ b/src/assets/images/product-chatbot.svg @@ -0,0 +1,21 @@ + + + + icon-product-app + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/App/App.scss b/src/components/App/App.scss index 409336184..ab5bfe4e1 100644 --- a/src/components/App/App.scss +++ b/src/components/App/App.scss @@ -2,7 +2,7 @@ body { min-width: 320px; - background-color: $tc-gray-neutral-dark; + background-color: $tc-gray-neutral-light; } .ReactModal__Body--open { diff --git a/src/components/TopBar/ProjectsToolBar.scss b/src/components/TopBar/ProjectsToolBar.scss index 85a8f1567..9ba4303fd 100644 --- a/src/components/TopBar/ProjectsToolBar.scss +++ b/src/components/TopBar/ProjectsToolBar.scss @@ -47,11 +47,12 @@ $tc-body-large : 20px; display: flex; align-items: center; // justify-content: center; - padding: 5px 2*$base-unit 5px 34px; + padding: 0 2*$base-unit 0 34px; text-align: left; color: $tc-gray-40; background-color: transparent; border-radius: 2px; + border: 0; font-size: 12px; position: relative; &:before{ @@ -173,6 +174,9 @@ $tc-body-large : 20px; top: 12px; z-index: 22;/* +1 from z-index of the modal overlay */ } + .FillProjectDetails .header { + margin: 35px 0 0; + } } .project-creation-dialog { diff --git a/src/config/constants.js b/src/config/constants.js index 48213103e..2257eda82 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -253,3 +253,5 @@ export const MAINTENANCE_MODE = false export const LS_INCOMPLETE_PROJECT = 'incompleteProject' export const CONNECT_MESSAGE_API_URL = process.env.CONNECT_MESSAGE_API_URL || TC_API_URL + +export const NEW_PROJECT_PATH = '/new-project' \ No newline at end of file diff --git a/src/config/projectQuestions/app_dev.v1.0.js b/src/config/projectQuestions/app_dev.v1.0.js index 3b73d7978..5d6e9b072 100644 --- a/src/config/projectQuestions/app_dev.v1.0.js +++ b/src/config/projectQuestions/app_dev.v1.0.js @@ -30,7 +30,7 @@ const sections = [ { id: 'projectName', required: true, - validationError: 'Please provide a name to your project', + validationError: 'Please provide a name for your project', fieldName: 'name', description: '', title: 'Project Name', @@ -240,7 +240,7 @@ export const basicSections = [ { id: 'projectName', required: true, - validationError: 'Please provide a name to your project', + validationError: 'Please provide a name for your project', fieldName: 'name', description: '', title: 'Project Name', diff --git a/src/config/projectQuestions/avd.v1.0.js b/src/config/projectQuestions/avd.v1.0.js index 7eae6bdb8..9a33c9419 100644 --- a/src/config/projectQuestions/avd.v1.0.js +++ b/src/config/projectQuestions/avd.v1.0.js @@ -32,7 +32,7 @@ const sections = [ { id: 'projectName', required: true, - validationError: 'Please provide a name to your project', + validationError: 'Please provide a name for your project', fieldName: 'name', description: '', title: 'Project Name', @@ -239,7 +239,7 @@ export const basicSections = [ { id: 'projectName', required: true, - validationError: 'Please provide a name to your project', + validationError: 'Please provide a name for your project', fieldName: 'name', description: '', title: 'Project Name', diff --git a/src/config/projectQuestions/ibm_chatbot.v1.0.js b/src/config/projectQuestions/ibm_chatbot.v1.0.js new file mode 100644 index 000000000..9b729f5ae --- /dev/null +++ b/src/config/projectQuestions/ibm_chatbot.v1.0.js @@ -0,0 +1,257 @@ +import _ from 'lodash' +// import { Icons } from 'appirio-tech-react-components' +import { findProduct} from '../projectWizard' + +const isFileRequired = (project, subSections) => { + const subSection = _.find(subSections, (s) => s.type === 'questions') + const fields = _.filter(subSection.questions, q => q.type.indexOf('see-attached') > -1) + // iterate over all seeAttached type fields to check + // if any see attached is checked. + return _.some(_.map( + _.map(fields, 'fieldName'), + fn => _.get(project, `${fn}.seeAttached`) + )) +} + +const sections = [ + { + id: 'appDefinition', + title: (project, showProduct) => { + const product = _.get(project, 'details.products[0]') + if (showProduct && product) { + const prd = findProduct(product) + if (prd) return prd + } + return 'Definition' + }, + required: true, + description: 'Please answer a few basic questions about your project. You can also provide the needed information in a supporting document--add a link in the notes section or upload it below.', + subSections: [ + { + id: 'projectName', + required: true, + validationError: 'Please provide a name for your project', + fieldName: 'name', + description: '', + title: 'Project Name', + type: 'project-name' + }, + { + id: 'questions', + required: true, + hideTitle: true, + title: 'Questions', + description: '', + type: 'questions', + questions: [ + { + icon: 'question', + title: 'Are you an existing Watson Virtual Assistant customer?', + description: '', + fieldName: 'details.appDefinition.existingWatsonCustomer', + type: 'checkbox', + options: [ + {value: 'true', title: 'Yes'}, + {value: 'false', title: 'No'}, + ] + }, + { + id: 'projectInfo', + required: true, + fieldName: 'description', + description: 'Brief Description', + title: 'Description', + type: 'textbox' + }, + { + icon: 'question', + title: 'Do you have an existing IBM Bluemix account?', + description: '', + type: 'radio-group', + fieldName: 'details.appDefinition.hasBluemixAccount', + options: [ + {value: 'true', label: 'Yes'}, + {value: 'false', label: 'No'} + ] + }, + { + icon: 'question', + title: 'What capabilities does the chatbot need to support? (check all that apply)', + description: '', + type: 'checkbox-group', + fieldName: 'details.appDefinition.capabilities', + options: [ + {value: 'order_management', label: 'Order Management'}, + {value: 'information', label: 'Information'}, + {value: 'help', label: 'Help'}, + {value: 'complaints', label: 'Complaints'}, + {value: 'billing', label: 'Billing'}, + {value: 'account_management', label: 'Account Management'}, + {value: 'custom', label: 'Custom'} + ], + }, + { + icon: 'question', + title: 'Will the chatbot need to provide data from any systems to support the capabilities you listed above?', + description: '', + type: 'radio-group', + fieldName: 'details.appDefinition.integrationSystems', + options: [ + {value: 'true', label: 'Yes'}, + {value: 'false', label: 'No'} + ] + }, + { + icon: 'question', + title: 'Do you have existing agent scripts?', + description: '', + type: 'radio-group', + fieldName: 'details.appDefinition.existingAgentScripts', + options: [ + {value: 'true', label: 'Yes'}, + {value: 'false', label: 'No'} + ] + }, + { + icon: 'question', + title: 'Are you planning to transfer conversations to human agents?', + description: '', + type: 'radio-group', + fieldName: 'details.appDefinition.transferToHumanAgents', + options: [ + {value: 'true', label: 'Yes'}, + {value: 'false', label: 'No'} + ] + } + ] + }, + { + id: 'notes', + fieldName: 'details.appDefinition.notes', + title: 'Notes', + description: 'Add any other important information regarding your project (e.g., links to documents or existing applications, budget or timing constraints)', + type: 'notes' + }, + { + id: 'files', + required: isFileRequired, + title: (project) => `Project Files (${_.get(project, 'attachments', []).length})` || 'Files', + description: '', + type: 'files', + fieldName: 'attachments' + } + ] + } +] + +export default sections + +export const basicSections = [ + { + id: 'appDefinition', + title: '', + required: true, + description: 'Please answer a few basic questions about your project and, as an option, add links to supporting documents in the “Notes” section. If you have any files to upload, you’ll be able to do so later.', + subSections: [ + { + id: 'projectName', + required: true, + validationError: 'Please provide a name for your project', + fieldName: 'name', + description: '', + title: 'Project Name', + type: 'project-name' + }, + { + id: 'questions', + required: true, + hideTitle: true, + title: 'Questions', + description: '', + type: 'questions', + questions: [ + { + id: 'projectInfo', + required: true, + fieldName: 'description', + description: 'Brief Description', + title: 'Description', + type: 'textbox' + }, + { + icon: 'question', + required: true, + title: 'Are you an existing Watson Virtual Assistant customer?', + description: '', + fieldName: 'details.appDefinition.existingWatsonCustomer', + type: 'radio-group', + options: [ + {value: 'true', label: 'Yes'}, + {value: 'false', label: 'No'}, + ] + }, + { + icon: 'question', + required: true, + title: 'Do you have an existing IBM Bluemix account?', + description: '', + type: 'radio-group', + fieldName: 'details.appDefinition.hasBluemixAccount', + options: [ + {value: 'true', label: 'Yes'}, + {value: 'false', label: 'No'} + ] + }, + { + icon: 'question', + // required: true, + title: 'What capabilities does the chatbot need to support?', + description: '', + type: 'checkbox-group', + fieldName: 'details.appDefinition.capabilities', + options: [ + {value: 'order_management', label: 'Order management'}, + {value: 'information', label: 'Information'}, + {value: 'help', label: 'Help'}, + {value: 'complaints', label: 'Complaints'}, + {value: 'billing', label: 'Billing'}, + {value: 'account_management', label: 'Account management'}, + {value: 'custom', label: 'Custom (please explain in the Notes)'} + ], + }, + { + icon: 'question', + required: true, + title: 'Will the chatbot need to access data from any systems to support the capabilities you listed above? If so, please list the systems below. (Change to text box)', + description: '', + type: 'textbox', + fieldName: 'details.appDefinition.integrationSystems' + }, + { + icon: 'question', + required: true, + title: 'Do you have any example agent conversations you can provide? If so, please paste them or any links to documents below (you’ll be able to upload documents later).', + description: '', + type: 'textbox', + fieldName: 'details.appDefinition.existingAgentScripts' + }, + { + icon: 'question', + required: true, + title: 'Are you planning to transfer conversations to human agents? If so, please list the agents’ communication tools (e.g., Slack, LiveAgent, Intercom, etc.)', + description: '', + type: 'textbox', + fieldName: 'details.appDefinition.transferToHumanAgents' + } + ] + }, + { + id: 'notes', + fieldName: 'details.appDefinition.notes', + title: 'Notes', + description: 'Add any other important information regarding your project (e.g., links to documents or existing applications, budget or timing constraints)', + type: 'notes' + } + ] + } +] \ No newline at end of file diff --git a/src/config/projectQuestions/topcoder.v1.js b/src/config/projectQuestions/topcoder.v1.js index 60f686bec..fe9c8be96 100644 --- a/src/config/projectQuestions/topcoder.v1.js +++ b/src/config/projectQuestions/topcoder.v1.js @@ -22,7 +22,7 @@ const sections = [ { id: 'projectName', required: true, - validationError: 'Please provide a name to your project', + validationError: 'Please provide a name for your project', fieldName: 'name', description: '', title: 'Project Name', @@ -214,7 +214,7 @@ export const basicSections = [ { id: 'projectName', required: true, - validationError: 'Please provide a name to your project', + validationError: 'Please provide a name for your project', fieldName: 'name', description: '', title: 'Project Name', diff --git a/src/config/projectQuestions/visual_design.v1.0.js b/src/config/projectQuestions/visual_design.v1.0.js index 85a338288..d45ee8097 100644 --- a/src/config/projectQuestions/visual_design.v1.0.js +++ b/src/config/projectQuestions/visual_design.v1.0.js @@ -33,7 +33,7 @@ const sections = [ { id: 'projectName', required: true, - validationError: 'Please provide a name to your project', + validationError: 'Please provide a name for your project', fieldName: 'name', description: '', title: 'Project Name', @@ -257,7 +257,7 @@ export const basicSections = [ { id: 'projectName', required: true, - validationError: 'Please provide a name to your project', + validationError: 'Please provide a name for your project', fieldName: 'name', description: '', title: 'Project Name', diff --git a/src/config/projectQuestions/wireframes.v1.0.js b/src/config/projectQuestions/wireframes.v1.0.js index f8659ba36..c877fbe35 100644 --- a/src/config/projectQuestions/wireframes.v1.0.js +++ b/src/config/projectQuestions/wireframes.v1.0.js @@ -33,7 +33,7 @@ const sections = [ { id: 'projectName', required: true, - validationError: 'Please provide a name to your project', + validationError: 'Please provide a name for your project', fieldName: 'name', description: '', title: 'Project Name', @@ -240,7 +240,7 @@ export const basicSections = [ { id: 'projectName', required: true, - validationError: 'Please provide a name to your project', + validationError: 'Please provide a name for your project', fieldName: 'name', description: '', title: 'Project Name', diff --git a/src/config/projectSpecification/typeToSpecification.json b/src/config/projectSpecification/typeToSpecification.json index 1269df54f..832520070 100644 --- a/src/config/projectSpecification/typeToSpecification.json +++ b/src/config/projectSpecification/typeToSpecification.json @@ -7,5 +7,6 @@ "visual_prototype": "app_dev.v1.0", "website_development": "app_dev.v1.0", "application_development": "app_dev.v1.0", + "watson_chatbot": "ibm_chatbot.v1.0", "generic_dev": "app_dev.v1.0" } diff --git a/src/config/projectWizard/index.js b/src/config/projectWizard/index.js index 8e1341b27..f6e97c501 100644 --- a/src/config/projectWizard/index.js +++ b/src/config/projectWizard/index.js @@ -1,3 +1,6 @@ +import _ from 'lodash' +import typeToSpecification from '../projectSpecification/typeToSpecification' + const products = { Design: { icon: 'product-app-visual-design', @@ -63,6 +66,13 @@ const products = { icon: 'product-app', id: 'application_development' }, + 'Watson Chatbot': { + brief: 'Watson Chatbot', + details: 'Build Chatbot using IBM Watson', + icon: 'product-chatbot', + id: 'watson_chatbot', + hidden: true + }, 'Software Development': { brief: 'Tasks or adhoc', details: 'Get help with any part of your development cycle', @@ -107,3 +117,32 @@ export function findProductCategory(product) { } } } + +/** + * Finds field from the project creation template + * + * @param {string} product id of the product. It should resolve to a template where search is to be made. + * @param {string} sectionId id of the section in the product template + * @param {string} subSectionId id of the sub section under the section identified by sectionId + * @param {string} fieldName name of the field to be fetched + * + * @return {object} field from the template, if found, null otherwise + */ +export function getProjectCreationTemplateField(product, sectionId, subSectionId, fieldName) { + let specification = 'topcoder.v1' + if (product) + specification = typeToSpecification[product] + const sections = require(`../projectQuestions/${specification}`).basicSections + const section = _.find(sections, {id: sectionId}) + let subSection = null + if (subSectionId && section) { + subSection = _.find(section.subSections, {id : subSectionId }) + } + if (subSection) { + if (subSectionId === 'questions') { + return _.find(subSection.questions, { fieldName }) + } + return subSection.fieldName === fieldName ? subSection : null + } + return null + } \ No newline at end of file diff --git a/src/projects/create/components/FillProjectDetails.scss b/src/projects/create/components/FillProjectDetails.scss index dd7c32d60..4a0a35a2a 100644 --- a/src/projects/create/components/FillProjectDetails.scss +++ b/src/projects/create/components/FillProjectDetails.scss @@ -28,6 +28,7 @@ .header { display: flex; justify-content: space-between; + margin-bottom: 30px; h1 { @include tc-heading; diff --git a/src/projects/create/components/ProjectWizard.jsx b/src/projects/create/components/ProjectWizard.jsx index 7f08533e4..b1b4c7709 100644 --- a/src/projects/create/components/ProjectWizard.jsx +++ b/src/projects/create/components/ProjectWizard.jsx @@ -2,13 +2,13 @@ import _ from 'lodash' import { unflatten } from 'flat' import React, { Component, PropTypes } from 'react' -import config, { findProductCategory } from '../../../config/projectWizard' +import config, { findProductCategory, getProjectCreationTemplateField } from '../../../config/projectWizard' import Wizard from '../../../components/Wizard' import SelectProduct from './SelectProduct' import IncompleteProjectConfirmation from './IncompleteProjectConfirmation' import FillProjectDetails from './FillProjectDetails' import update from 'react-addons-update' -import { LS_INCOMPLETE_PROJECT } from '../../../config/constants' +import { LS_INCOMPLETE_PROJECT, PROJECT_REF_CODE_MAX_LENGTH } from '../../../config/constants' import './ProjectWizard.scss' const WZ_STEP_INCOMP_PROJ_CONF = 0 @@ -36,10 +36,11 @@ class ProjectWizard extends Component { this.removeIncompleteProject = this.removeIncompleteProject.bind(this) this.handleOnCreateProject = this.handleOnCreateProject.bind(this) this.handleStepChange = this.handleStepChange.bind(this) + this.restoreCommonDetails = this.restoreCommonDetails.bind(this) } componentDidMount() { - const { params, onStepChange } = this.props + const { params, onStepChange, location } = this.props // load incomplete project from local storage const incompleteProjectStr = window.localStorage.getItem(LS_INCOMPLETE_PROJECT) if(incompleteProjectStr) { @@ -64,6 +65,17 @@ class ProjectWizard extends Component { wizardStep = WZ_STEP_FILL_PROJ_DETAILS } } + // retrieve refCode from query param + // TODO give warning after truncating + const refCode = _.get(location, 'query.refCode', '').trim().substr(0, PROJECT_REF_CODE_MAX_LENGTH) + if (refCode.trim().length > 0) { + // if refCode exists, update the updateQuery to set that refCode + if (_.get(updateQuery, 'details')) { + updateQuery['details']['utm'] = { $set : { code : refCode }} + } else { + updateQuery['details'] = { utm : { $set : { code : refCode }}} + } + } this.setState({ project: update(this.state.project, updateQuery), dirtyProject: update(this.state.dirtyProject, updateQuery), @@ -94,7 +106,7 @@ class ProjectWizard extends Component { * It also moves the wizard to the project details step if there exists an incomplete project. */ loadIncompleteProject() { - const { onStepChange } = this.props + const { onStepChange, onProjectUpdate } = this.props const incompleteProjectStr = window.localStorage.getItem(LS_INCOMPLETE_PROJECT) if(incompleteProjectStr) { const incompleteProject = JSON.parse(incompleteProjectStr) @@ -103,6 +115,7 @@ class ProjectWizard extends Component { dirtyProject: update(this.state.dirtyProject, { $merge : incompleteProject }), wizardStep: WZ_STEP_FILL_PROJ_DETAILS }, () => { + typeof onProjectUpdate === 'function' && onProjectUpdate(this.state.dirtyProject, false) typeof onStepChange === 'function' && onStepChange(this.state.wizardStep) }) } @@ -137,12 +150,13 @@ class ProjectWizard extends Component { updateProducts(projectType, product) { window.scrollTo(0, 0) - const { onStepChange } = this.props - const products = _.get(this.state.project, 'details.products') - let updateQuery = { details: { products : { $set : [product] }}} - if (!products) { - updateQuery = { details: { $set : { products : [product] }}} - } + const { onStepChange, onProjectUpdate } = this.props + // const products = _.get(this.state.project, 'details.products') + const updateQuery = { } + const detailsQuery = { products : [product] } + // restore common fields from dirty project + this.restoreCommonDetails(product, updateQuery, detailsQuery) + updateQuery.details = { $set : detailsQuery} if (projectType) { updateQuery.type = {$set : projectType } } @@ -151,10 +165,72 @@ class ProjectWizard extends Component { dirtyProject: update(this.state.project, updateQuery), wizardStep: WZ_STEP_FILL_PROJ_DETAILS }, () => { + typeof onProjectUpdate === 'function' && onProjectUpdate(this.state.dirtyProject, false) typeof onStepChange === 'function' && onStepChange(this.state.wizardStep) }) } + /** + * Restores common details of the project while changing product type. + * + * Added for Github issue#1037 + */ + restoreCommonDetails(updatedProduct, updateQuery, detailsQuery) { + const name = _.get(this.state.dirtyProject, 'name') + // if name was already entered, restore it + if (name) { + updateQuery.name = { $set: name } + } + const description = _.get(this.state.dirtyProject, 'description') + // if description was already entered, restore it + if (description) { + updateQuery.description = { $set: description } + } + const utm = _.get(this.state.dirtyProject, 'details.utm') + // if UTM code was already entered, restore it + if (utm) { + detailsQuery.utm = { code : utm.code } + } + const appDefinitionQuery = {} + const goal = _.get(this.state.dirtyProject, 'details.appDefinition.goal') + // finds the goal field from the updated product template + const goalField = getProjectCreationTemplateField( + updatedProduct, + 'appDefinition', + 'questions', + 'details.appDefinition.goal.value' + ) + // if goal was already entered and updated product template has the field, restore it + if (goalField && goal) { + appDefinitionQuery.goal = goal + } + const users = _.get(this.state.dirtyProject, 'details.appDefinition.users') + // finds the users field from the target product template + const usersField = getProjectCreationTemplateField( + updatedProduct, + 'appDefinition', + 'questions', + 'details.appDefinition.users.value' + ) + // if users was already entered and updated product template has the field, restore it + if (usersField && users) { + appDefinitionQuery.users = users + } + const notes = _.get(this.state.dirtyProject, 'details.appDefinition.notes') + // finds the notes field from the target product template + const notesField = getProjectCreationTemplateField( + updatedProduct, + 'appDefinition', + 'notes', + 'details.appDefinition.notes' + ) + // if notes was already entered and updated product template has the field, restore it + if (notesField && notes) { + appDefinitionQuery.notes = notes + } + detailsQuery.appDefinition = appDefinitionQuery + } + handleProjectChange(change) { const { onProjectUpdate } = this.props this.setState({ diff --git a/src/projects/create/components/ProjectWizard.scss b/src/projects/create/components/ProjectWizard.scss index d094be956..522caa70b 100644 --- a/src/projects/create/components/ProjectWizard.scss +++ b/src/projects/create/components/ProjectWizard.scss @@ -5,6 +5,7 @@ background: transparent; box-shadow: none; width: 100%; + margin-top: -30px; .ProjectTypeCard { background: $tc-white; diff --git a/src/projects/create/components/SelectProduct.js b/src/projects/create/components/SelectProduct.js index e8bdd1c39..1d0af5a4c 100644 --- a/src/projects/create/components/SelectProduct.js +++ b/src/projects/create/components/SelectProduct.js @@ -15,7 +15,8 @@ function SelectProduct(props) { for(const subType in subTypes) { const item = subTypes[subType] // don't render disabled items for selection - if (item.disabled) continue + // don't render hidden items as well, hidden items can be reached via direct link though + if (item.disabled || item.hidden) continue const icon = cards.push( this.setState({ - wizardStep - })} - onProjectUpdate={ (updatedProject) => this.setState({ - isProjectDirty: true, - updatedProject - })} + onStepChange={ (wizardStep) => { + if (wizardStep === ProjectWizard.Steps.WZ_STEP_INCOMP_PROJ_CONF) { + browserHistory.push(NEW_PROJECT_PATH + '/incomplete') + } + if (wizardStep === ProjectWizard.Steps.WZ_STEP_SELECT_PROD_TYPE) { + browserHistory.push(NEW_PROJECT_PATH +'' + window.location.search) + } + this.setState({ + wizardStep + }) + } + } + onProjectUpdate={ (updatedProject, dirty=true) => { + const prevProduct = _.get(this.state.updatedProject, 'details.products[0]', null) + const product = _.get(updatedProject, 'details.products[0]', null) + // compares updated product with previous product to know if user has updated the product + if (prevProduct !== product) { + if (product) { + browserHistory.push(NEW_PROJECT_PATH + '/' + product + window.location.search) + } + } + this.setState({ + isProjectDirty: dirty, + updatedProject + }) + } + } /> ) } diff --git a/src/projects/detail/components/SpecSection.jsx b/src/projects/detail/components/SpecSection.jsx index 3ea0bf76f..3f2396a29 100644 --- a/src/projects/detail/components/SpecSection.jsx +++ b/src/projects/detail/components/SpecSection.jsx @@ -1,4 +1,5 @@ import React, { PropTypes } from 'react' +import qs from 'query-string' import { Tabs, Tab, TCFormFields } from 'appirio-tech-react-components' import _ from 'lodash' import SpecQuestions from './SpecQuestions' @@ -98,6 +99,7 @@ const SpecSection = props => { case 'project-name': { const refCodeFieldName = 'details.utm.code' const refCode = _.get(project, refCodeFieldName, undefined) + const queryParamRefCode = qs.parse(window.location.search).refCode return (
{ (!project.status || project.status === PROJECT_STATUS_DRAFT) && @@ -129,6 +131,7 @@ const SpecSection = props => { wrapperClass="project-refcode" maxLength={ PROJECT_REF_CODE_MAX_LENGTH } theme="paper-form-dotted" + disabled={ queryParamRefCode && queryParamRefCode.length > 0 } />
Optional diff --git a/src/projects/detail/containers/Specification.scss b/src/projects/detail/containers/Specification.scss index 479c254fc..f5c1ac7fc 100644 --- a/src/projects/detail/containers/Specification.scss +++ b/src/projects/detail/containers/Specification.scss @@ -188,6 +188,10 @@ $title-color: $tc-gray-80; border-radius: 4px; position: relative; + form > div { + box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2); + } + .title { color: $tc-gray-80; @include roboto-bold; diff --git a/src/routes.jsx b/src/routes.jsx index a3fdf65ae..756340b31 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -30,6 +30,12 @@ browserHistory.listen(location => { window.analytics.page('Project Specification') } else if (/^\/$/.test(location.pathname)) { window.analytics.page('Connect Home') + } else if (/^new-project\/$/.test(location.pathname)) { + window.analytics.page('New Project : Select Product') + } else if (/^new-project\/incomplete$/.test(location.pathname)) { + window.analytics.page('New Project : Incomplete Project') + } else if (/^new-project\/[a-zA-Z0-9\_]+$/.test(location.pathname)) { + window.analytics.page('New Project : Project Details') } } }) @@ -81,9 +87,13 @@ const validateCreateProjectParams = (nextState, replace, callback) => { const product = nextState.params.product const productCategory = findProductCategory(product) if (product && product.trim().length > 0 && !productCategory) { - replace('/404') + // workaround to add URL for incomplete project confirmation step + // ideally we should have better URL naming which resolves each route with distinct patterns + if (product !== 'incomplete') { + replace('/404') + } } - callback() + callback() } const renderTopBarWithProjectsToolBar = () =>