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
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ workflows:
- build-dev
filters:
branches:
only: ['dev', 'feature/cf-2.20']
only: ['dev', 'feature/billing_account_protection']

- deployTest01:
context : org-global
Expand Down
11 changes: 11 additions & 0 deletions src/api/billingAccounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,14 @@ export function fetchBillingAccounts(projectId) {
return axios.get(`${TC_API_URL}/v5/projects/${projectId}/billingAccounts`)
}


/**
* Get billing account based on project id
*
* @param {String} projectId Id of the project
*
* @returns {Promise<Object>} Billing account data
*/
export function fetchBillingAccount(projectId) {
return axios.get(`${TC_API_URL}/v5/projects/${projectId}/billingAccount`)
}
13 changes: 13 additions & 0 deletions src/assets/icons/v.2.5/icon-warning.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions src/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ export const CLEAR_PROJECT_SUGGESTIONS_SEARCH = 'CLEAR_PROJECT_SUGGESTIONS_SEA
export const PROJECT_SUGGESTIONS_SEARCH_FAILURE = 'PROJECT_SUGGESTIONS_SEARCH_FAILURE'
export const PROJECT_SUGGESTIONS_SEARCH_SUCCESS = 'PROJECT_SUGGESTIONS_SEARCH_SUCCESS'


// project billingAccount
export const LOAD_PROJECT_BILLING_ACCOUNT = 'LOAD_PROJECT_BILLING_ACCOUNT'
export const LOAD_PROJECT_BILLING_ACCOUNT_PENDING = 'LOAD_PROJECT_BILLING_ACCOUNT_PENDING'
export const LOAD_PROJECT_BILLING_ACCOUNT_FAILURE = 'LOAD_PROJECT_BILLING_ACCOUNT_FAILURE'
export const LOAD_PROJECT_BILLING_ACCOUNT_SUCCESS = 'LOAD_PROJECT_BILLING_ACCOUNT_SUCCESS'

// Project Dashboard
export const LOAD_PROJECT_DASHBOARD = 'LOAD_PROJECT_DASHBOARD'
export const LOAD_PROJECT_DASHBOARD_PENDING = 'LOAD_PROJECT_DASHBOARD_PENDING'
Expand Down
10 changes: 8 additions & 2 deletions src/helpers/projectHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ 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 WarningIcon from '../assets/icons/v.2.5/icon-warning.svg'
import InvisibleIcon from '../assets/icons/invisible.svg'

import { formatNumberWithCommas } from './format'
Expand Down Expand Up @@ -259,8 +260,9 @@ export function getNewProjectLink(orgConfigs) {
* Get the list of navigation links for project details view
* @param {Object} project - The project object
* @param {string} projectId - The project id
* @param {boolean} isBillingAccountExpired - is billingAccount expired
*/
export function getProjectNavLinks(project, projectId, renderFAQs) {
export function getProjectNavLinks(project, projectId, renderFAQs, isBillingAccountExpired) {
let messagesTab = null
// `Discussions` items can be added as soon as project is loaded
// if discussions are not hidden for it
Expand Down Expand Up @@ -289,7 +291,11 @@ export function getProjectNavLinks(project, projectId, renderFAQs) {
}

if (hasPermission(PERMISSIONS.VIEW_PROJECT_SETTINGS)) {
navLinks.push({ label: 'Project Settings', to: `/projects/${projectId}/settings`, Icon: AccountSecurityIcon, iconClassName: 'stroke' })
if (!isBillingAccountExpired) {
navLinks.push({ label: 'Project Settings', to: `/projects/${projectId}/settings`, Icon: AccountSecurityIcon, iconClassName: 'stroke' })
}else {
navLinks.push({ label: <span>Project Settings <WarningIcon style={{position: 'absolute', marginLeft: '5px'}}/></span>, to: `/projects/${projectId}/settings`, Icon: AccountSecurityIcon, iconClassName: 'stroke' })
}
}

return navLinks
Expand Down
11 changes: 11 additions & 0 deletions src/projects/actions/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { getProjectById,
createScopeChangeRequest as createScopeChangeRequestAPI,
updateScopeChangeRequest as updateScopeChangeRequestAPI,
} from '../../api/projects'
import {fetchBillingAccount} from '../../api/billingAccounts'
import {
getProjectInviteById,
getProjectMemberInvites,
Expand All @@ -26,6 +27,7 @@ import {
} from '../../api/projectMembers'
// import { loadProductTimelineWithMilestones } from './productsTimelines'
import {
LOAD_PROJECT_BILLING_ACCOUNT,
LOAD_PROJECT,
LOAD_PROJECT_MEMBER_INVITE,
CREATE_PROJECT,
Expand Down Expand Up @@ -661,6 +663,15 @@ export function loadProjectMembers(projectId) {
}
}

export function loadProjectBillingAccount(projectId) {
return (dispatch) => {
return dispatch({
type: LOAD_PROJECT_BILLING_ACCOUNT,
payload: fetchBillingAccount(projectId)
})
}
}

export function loadProjectMemberInvites(projectId) {
return (dispatch) => {
return dispatch({
Expand Down
5 changes: 4 additions & 1 deletion src/projects/actions/projectDashboard.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import _ from 'lodash'
import { loadProject, loadProjectInvite, loadProjectMembers, loadProjectMemberInvites } from './project'
import { loadProject, loadProjectBillingAccount, loadProjectInvite, loadProjectMembers, loadProjectMemberInvites } from './project'
import { loadProjectPlan } from './projectPlan'
import { loadProjectsMetadata } from '../../actions/templates'
import { LOAD_PROJECT_DASHBOARD, LOAD_ADDITIONAL_PROJECT_DATA } from '../../config/constants'
Expand Down Expand Up @@ -27,10 +27,13 @@ const getDashboardData = (dispatch, getState, projectId, isOnlyLoadProjectInfo)
//dispatch(loadMembers(userIds)),
dispatch(loadProjectMembers(projectId)),
dispatch(loadProjectMemberInvites(projectId))

]
if (isOnlyLoadProjectInfo) {
promises = []
}
// load project billingAccounts
promises.push(dispatch(loadProjectBillingAccount(projectId)))

// for new projects load phases, products, project template and product templates
if (['v3', 'v4'].indexOf(project.version) !== -1) {
Expand Down
15 changes: 15 additions & 0 deletions src/projects/detail/components/BillingAccountField/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import moment from 'moment'
import _ from 'lodash'
import {HOC as hoc} from 'formsy-react'

import Select from '../../../../components/Select/Select'
Expand Down Expand Up @@ -27,6 +28,8 @@ class BillingAccountField extends React.Component {
isLoading: true,
billingAccounts: [],
selectedBillingAccount: null,
defaultBillingAccount: null,
isDefaultBillingAccountExpired: false,
}

this.handleChange = this.handleChange.bind(this)
Expand Down Expand Up @@ -60,6 +63,10 @@ class BillingAccountField extends React.Component {
})

billingAccounts = [selectedBillingAccount, ...billingAccounts]
this.setState({
defaultBillingAccount: selectedBillingAccount,
isDefaultBillingAccountExpired: this.props.isExpired
})
}
}

Expand All @@ -74,9 +81,16 @@ class BillingAccountField extends React.Component {
handleChange(value) {
this.setState({ selectedBillingAccount: value })
this.props.setValue(value ? value.value : null)
this.props.setBillingAccountExpired && this.props.setBillingAccountExpired(this.isCurrentBillingAccountExpired(value))
}

isCurrentBillingAccountExpired(nextSelectedBillingAccount) {
const {defaultBillingAccount, isDefaultBillingAccountExpired, selectedBillingAccount} = this.state
return isDefaultBillingAccountExpired && _.get(defaultBillingAccount, 'value') === _.get(nextSelectedBillingAccount || selectedBillingAccount, 'value')
}

render() {

const placeholder = this.state.billingAccounts.length > 0
? 'Select billing account'
: 'No Billing Account Available'
Expand All @@ -92,6 +106,7 @@ class BillingAccountField extends React.Component {
isDisabled={this.state.billingAccounts.length === 0}
showDropdownIndicator
/>
{this.isCurrentBillingAccountExpired() && <span className="error-message">Expired</span>}
{this.state.selectedBillingAccount && (
<div className={styles.manageBillingAccountLinkWrapper}>
<a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {connect} from 'react-redux'
import _ from 'lodash'
import FormsyForm from 'appirio-tech-react-components/components/Formsy'
const Formsy = FormsyForm.Formsy
import { updateProject } from '../../../actions/project'
import { updateProject, loadProjectBillingAccount } from '../../../actions/project'
import NDAField from '../NDAField'
import GroupsField from '../GroupsField'
import BillingAccountField from '../BillingAccountField'
Expand All @@ -18,11 +18,13 @@ class EditProjectDefaultsForm extends React.Component {

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

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

componentDidMount() {
Expand All @@ -35,6 +37,11 @@ class EditProjectDefaultsForm extends React.Component {
}
}

setBillingAccountExpired(value) {
this.setState({
isBillingAccountExpired: value
})
}
handleChange(changed) {
const keys = _.intersection(Object.keys(changed), Object.keys(this.state.project))
const reqProjectState = keys.reduce((acc, curr) => {
Expand All @@ -52,7 +59,7 @@ class EditProjectDefaultsForm extends React.Component {
}

handleSubmit() {
const {updateProject} = this.props
const {updateProject, loadProjectBillingAccount} = this.props
const {id} = this.props.project
const updateProjectObj = Object.keys(this.state.project)
.filter(key => !_.isEqual(this.props.project[key], this.state.project[key]))
Expand All @@ -61,7 +68,7 @@ class EditProjectDefaultsForm extends React.Component {
return acc
}, {})
updateProject(id, updateProjectObj)
.then(() => this.setState({enableButton: false}))
.then(() => this.setState({enableButton: false})).then(() => loadProjectBillingAccount(id))
.catch(console.error)
}

Expand All @@ -86,14 +93,16 @@ class EditProjectDefaultsForm extends React.Component {
<BillingAccountField
name="billingAccountId"
projectId={this.state.project.id}
isExpired={this.props.isBillingAccountExpired}
value={this.state.project.billingAccountId}
setBillingAccountExpired={this.setBillingAccountExpired}
/>
</div>
<div className="section-footer section-footer-spec">
<button
className="tc-btn tc-btn-primary tc-btn-md"
type="submit"
disabled={!this.state.enableButton}
disabled={this.state.isBillingAccountExpired || !this.state.enableButton}
>
Save
</button>
Expand All @@ -105,7 +114,8 @@ class EditProjectDefaultsForm extends React.Component {
}

const mapDispatchToProps = {
updateProject
updateProject,
loadProjectBillingAccount
}

export default protectComponent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ class CreateMilestoneForm extends React.Component {
cancelEdit() {
const { previousMilestone } = this.props
const startDate = previousMilestone ? moment.utc(previousMilestone.completionDate || previousMilestone.endDate) : moment.utc()
this.setState({
this.state = {
isEditing: false,
type: '',
name: '',
startDate: startDate.format('YYYY-MM-DD'),
endDate: startDate.add(3, 'days').format('YYYY-MM-DD')
})
}
}

onAddClick() {
Expand Down
4 changes: 3 additions & 1 deletion src/projects/detail/containers/ProjectDefaultsContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class ProjectDefaultsContainer extends Component {
isProcessing,
feeds,
isFeedsLoading,
isBillingAccountExpired,
productsTimelines,
phasesTopics,
location,
Expand Down Expand Up @@ -54,7 +55,7 @@ class ProjectDefaultsContainer extends Component {
</MediaQuery>
</TwoColsLayout.Sidebar>
<TwoColsLayout.Content>
<EditProjectDefaultsForm project={this.props.project} />
<EditProjectDefaultsForm project={this.props.project} isBillingAccountExpired={isBillingAccountExpired} />
</TwoColsLayout.Content>
</TwoColsLayout>
)
Expand All @@ -68,6 +69,7 @@ const mapStateToProps = ({ loadUser, projectState, projectTopics, topics }) => {

return {
user: loadUser.user,
isBillingAccountExpired: projectState.isBillingAccountExpired,
isProcessing: projectState.processing,
phases: projectState.phases,
phasesNonDirty: projectState.phasesNonDirty,
Expand Down
8 changes: 5 additions & 3 deletions src/projects/detail/containers/ProjectInfoContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class ProjectInfoContainer extends React.Component {
!_.isEqual(nextProps.feeds, this.props.feeds) ||
!_.isEqual(nextProps.phases, this.props.phases) ||
!_.isEqual(nextProps.productsTimelines, this.props.productsTimelines) ||
!_.isEqual(nextProps.isBillingAccountExpired, this.props.isBillingAccountExpired) ||
!_.isEqual(nextProps.phasesTopics, this.props.phasesTopics) ||
!_.isEqual(nextProps.isFeedsLoading, this.props.isFeedsLoading) ||
!_.isEqual(nextProps.isProjectProcessing, this.props.isProjectProcessing) ||
Expand Down Expand Up @@ -426,7 +427,7 @@ class ProjectInfoContainer extends React.Component {
render() {
const { showDeleteConfirm } = this.state
const { project, phases, hideInfo, hideMembers,
productsTimelines, isProjectProcessing, notifications, projectTemplates } = this.props
productsTimelines, isProjectProcessing, notifications, projectTemplates, isBillingAccountExpired } = this.props

const projectTemplateId = project.templateId
const projectTemplateKey = _.get(project, 'details.products[0]')
Expand Down Expand Up @@ -457,7 +458,7 @@ class ProjectInfoContainer extends React.Component {
const notReadAssetsNotifications = filterFileAndLinkChangedNotifications(projectNotReadNotifications)

const renderFAQs = containsFAQ(projectTemplate)
const navLinks = getProjectNavLinks(project, project.id, renderFAQs).map((navLink) => {
const navLinks = getProjectNavLinks(project, project.id, renderFAQs, isBillingAccountExpired).map((navLink) => {
if (navLink.label === 'Messages') {
navLink.count = notReadMessageNotifications.length
navLink.toolTipText = 'New messages'
Expand Down Expand Up @@ -593,10 +594,11 @@ ProjectInfoContainer.PropTypes = {
canAccessPrivatePosts: PropTypes.bool.isRequired,
}

const mapStateToProps = ({ templates, notifications }) => {
const mapStateToProps = ({ templates, projectState, notifications }) => {
const canAccessPrivatePosts = hasPermission(PERMISSIONS.ACCESS_PRIVATE_POST)
return ({
projectTemplates : templates.projectTemplates,
isBillingAccountExpired: projectState.isBillingAccountExpired,
canAccessPrivatePosts,
notifications: notifications.notifications,
})
Expand Down
12 changes: 12 additions & 0 deletions src/projects/reducers/project.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_PENDING, CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_SUCCESS, CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_FAILURE,
LOAD_PROJECT_PENDING, LOAD_PROJECT_SUCCESS, LOAD_PROJECT_MEMBER_INVITE_PENDING, LOAD_PROJECT_MEMBER_INVITE_SUCCESS, LOAD_PROJECT_FAILURE,
LOAD_PROJECT_BILLING_ACCOUNT_SUCCESS, LOAD_PROJECT_BILLING_ACCOUNT_FAILURE,
CREATE_PROJECT_PENDING, CREATE_PROJECT_SUCCESS, CREATE_PROJECT_FAILURE, CLEAR_LOADED_PROJECT,
UPDATE_PROJECT_PENDING, UPDATE_PROJECT_SUCCESS, UPDATE_PROJECT_FAILURE,
DELETE_PROJECT_PENDING, DELETE_PROJECT_SUCCESS, DELETE_PROJECT_FAILURE,
Expand Down Expand Up @@ -36,6 +37,7 @@ export function getEmptyProjectObject() {

const initialState = {
isLoading: true,
isBillingAccountExpired: false,
processing: false,
processingMembers: false,
updatingMemberIds: [],
Expand Down Expand Up @@ -151,6 +153,16 @@ export const projectState = function (state=initialState, action) {
return Object.assign({}, state, {
isCreatingPhase: true
})
case LOAD_PROJECT_BILLING_ACCOUNT_SUCCESS:
return {
...state,
isBillingAccountExpired: !action.payload.data.active,
}
case LOAD_PROJECT_BILLING_ACCOUNT_FAILURE:
return {
...state,
isBillingAccountExpired: false,
}
case CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_SUCCESS:
case CREATE_PROJECT_PHASE_SUCCESS: {
const { phase, product } = action.payload
Expand Down
Loading