Skip to content

Commit 3431a0f

Browse files
author
vikasrohit
authored
Merge pull request #4395 from appirio-tech/dev
Prod Release - 2.21.0
2 parents 884b5d0 + 7ae9a4b commit 3431a0f

File tree

14 files changed

+115
-16
lines changed

14 files changed

+115
-16
lines changed

.circleci/config.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ workflows:
128128
- build-dev
129129
filters:
130130
branches:
131-
only: ['dev', 'feature/cf-2.20']
131+
only: ['dev', 'feature/billing_account_protection']
132132

133133
- deployTest01:
134134
context : org-global

src/api/billingAccounts.js

+11
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,14 @@ export function fetchBillingAccounts(projectId) {
1212
return axios.get(`${TC_API_URL}/v5/projects/${projectId}/billingAccounts`)
1313
}
1414

15+
16+
/**
17+
* Get billing account based on project id
18+
*
19+
* @param {String} projectId Id of the project
20+
*
21+
* @returns {Promise<Object>} Billing account data
22+
*/
23+
export function fetchBillingAccount(projectId) {
24+
return axios.get(`${TC_API_URL}/v5/projects/${projectId}/billingAccount`)
25+
}
+13
Loading

src/config/constants.js

+7
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ export const CLEAR_PROJECT_SUGGESTIONS_SEARCH = 'CLEAR_PROJECT_SUGGESTIONS_SEA
120120
export const PROJECT_SUGGESTIONS_SEARCH_FAILURE = 'PROJECT_SUGGESTIONS_SEARCH_FAILURE'
121121
export const PROJECT_SUGGESTIONS_SEARCH_SUCCESS = 'PROJECT_SUGGESTIONS_SEARCH_SUCCESS'
122122

123+
124+
// project billingAccount
125+
export const LOAD_PROJECT_BILLING_ACCOUNT = 'LOAD_PROJECT_BILLING_ACCOUNT'
126+
export const LOAD_PROJECT_BILLING_ACCOUNT_PENDING = 'LOAD_PROJECT_BILLING_ACCOUNT_PENDING'
127+
export const LOAD_PROJECT_BILLING_ACCOUNT_FAILURE = 'LOAD_PROJECT_BILLING_ACCOUNT_FAILURE'
128+
export const LOAD_PROJECT_BILLING_ACCOUNT_SUCCESS = 'LOAD_PROJECT_BILLING_ACCOUNT_SUCCESS'
129+
123130
// Project Dashboard
124131
export const LOAD_PROJECT_DASHBOARD = 'LOAD_PROJECT_DASHBOARD'
125132
export const LOAD_PROJECT_DASHBOARD_PENDING = 'LOAD_PROJECT_DASHBOARD_PENDING'

src/helpers/projectHelper.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import ReportsIcon from '../assets/icons/v.2.5/icon-reports.svg'
2020
import AssetsLibraryIcon from '../assets/icons/v.2.5/icon-assets-library.svg'
2121
import FAQIcon from '../assets/icons/faq.svg'
2222
import AccountSecurityIcon from 'assets/icons/v.2.5/icon-account-security.svg'
23+
import WarningIcon from '../assets/icons/v.2.5/icon-warning.svg'
2324
import InvisibleIcon from '../assets/icons/invisible.svg'
2425

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

291293
if (hasPermission(PERMISSIONS.VIEW_PROJECT_SETTINGS)) {
292-
navLinks.push({ label: 'Project Settings', to: `/projects/${projectId}/settings`, Icon: AccountSecurityIcon, iconClassName: 'stroke' })
294+
if (!isBillingAccountExpired) {
295+
navLinks.push({ label: 'Project Settings', to: `/projects/${projectId}/settings`, Icon: AccountSecurityIcon, iconClassName: 'stroke' })
296+
}else {
297+
navLinks.push({ label: <span>Project Settings <WarningIcon style={{position: 'absolute', marginLeft: '5px'}}/></span>, to: `/projects/${projectId}/settings`, Icon: AccountSecurityIcon, iconClassName: 'stroke' })
298+
}
293299
}
294300

295301
return navLinks

src/projects/actions/project.js

+11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { getProjectById,
1313
createScopeChangeRequest as createScopeChangeRequestAPI,
1414
updateScopeChangeRequest as updateScopeChangeRequestAPI,
1515
} from '../../api/projects'
16+
import {fetchBillingAccount} from '../../api/billingAccounts'
1617
import {
1718
getProjectInviteById,
1819
getProjectMemberInvites,
@@ -26,6 +27,7 @@ import {
2627
} from '../../api/projectMembers'
2728
// import { loadProductTimelineWithMilestones } from './productsTimelines'
2829
import {
30+
LOAD_PROJECT_BILLING_ACCOUNT,
2931
LOAD_PROJECT,
3032
LOAD_PROJECT_MEMBER_INVITE,
3133
CREATE_PROJECT,
@@ -661,6 +663,15 @@ export function loadProjectMembers(projectId) {
661663
}
662664
}
663665

666+
export function loadProjectBillingAccount(projectId) {
667+
return (dispatch) => {
668+
return dispatch({
669+
type: LOAD_PROJECT_BILLING_ACCOUNT,
670+
payload: fetchBillingAccount(projectId)
671+
})
672+
}
673+
}
674+
664675
export function loadProjectMemberInvites(projectId) {
665676
return (dispatch) => {
666677
return dispatch({

src/projects/actions/projectDashboard.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import _ from 'lodash'
2-
import { loadProject, loadProjectInvite, loadProjectMembers, loadProjectMemberInvites } from './project'
2+
import { loadProject, loadProjectBillingAccount, loadProjectInvite, loadProjectMembers, loadProjectMemberInvites } from './project'
33
import { loadProjectPlan } from './projectPlan'
44
import { loadProjectsMetadata } from '../../actions/templates'
55
import { LOAD_PROJECT_DASHBOARD, LOAD_ADDITIONAL_PROJECT_DATA } from '../../config/constants'
@@ -27,10 +27,13 @@ const getDashboardData = (dispatch, getState, projectId, isOnlyLoadProjectInfo)
2727
//dispatch(loadMembers(userIds)),
2828
dispatch(loadProjectMembers(projectId)),
2929
dispatch(loadProjectMemberInvites(projectId))
30+
3031
]
3132
if (isOnlyLoadProjectInfo) {
3233
promises = []
3334
}
35+
// load project billingAccounts
36+
promises.push(dispatch(loadProjectBillingAccount(projectId)))
3437

3538
// for new projects load phases, products, project template and product templates
3639
if (['v3', 'v4'].indexOf(project.version) !== -1) {

src/projects/detail/components/BillingAccountField/index.js

+15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react'
22
import moment from 'moment'
3+
import _ from 'lodash'
34
import {HOC as hoc} from 'formsy-react'
45

56
import Select from '../../../../components/Select/Select'
@@ -27,6 +28,8 @@ class BillingAccountField extends React.Component {
2728
isLoading: true,
2829
billingAccounts: [],
2930
selectedBillingAccount: null,
31+
defaultBillingAccount: null,
32+
isDefaultBillingAccountExpired: false,
3033
}
3134

3235
this.handleChange = this.handleChange.bind(this)
@@ -60,6 +63,10 @@ class BillingAccountField extends React.Component {
6063
})
6164

6265
billingAccounts = [selectedBillingAccount, ...billingAccounts]
66+
this.setState({
67+
defaultBillingAccount: selectedBillingAccount,
68+
isDefaultBillingAccountExpired: this.props.isExpired
69+
})
6370
}
6471
}
6572

@@ -74,9 +81,16 @@ class BillingAccountField extends React.Component {
7481
handleChange(value) {
7582
this.setState({ selectedBillingAccount: value })
7683
this.props.setValue(value ? value.value : null)
84+
this.props.setBillingAccountExpired && this.props.setBillingAccountExpired(this.isCurrentBillingAccountExpired(value))
85+
}
86+
87+
isCurrentBillingAccountExpired(nextSelectedBillingAccount) {
88+
const {defaultBillingAccount, isDefaultBillingAccountExpired, selectedBillingAccount} = this.state
89+
return isDefaultBillingAccountExpired && _.get(defaultBillingAccount, 'value') === _.get(nextSelectedBillingAccount || selectedBillingAccount, 'value')
7790
}
7891

7992
render() {
93+
8094
const placeholder = this.state.billingAccounts.length > 0
8195
? 'Select billing account'
8296
: 'No Billing Account Available'
@@ -92,6 +106,7 @@ class BillingAccountField extends React.Component {
92106
isDisabled={this.state.billingAccounts.length === 0}
93107
showDropdownIndicator
94108
/>
109+
{this.isCurrentBillingAccountExpired() && <span className="error-message">Expired</span>}
95110
{this.state.selectedBillingAccount && (
96111
<div className={styles.manageBillingAccountLinkWrapper}>
97112
<a

src/projects/detail/components/EditProjectDefaultsForm/EditProjectDefaultsForm.jsx

+16-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {connect} from 'react-redux'
33
import _ from 'lodash'
44
import FormsyForm from 'appirio-tech-react-components/components/Formsy'
55
const Formsy = FormsyForm.Formsy
6-
import { updateProject } from '../../../actions/project'
6+
import { updateProject, loadProjectBillingAccount } from '../../../actions/project'
77
import NDAField from '../NDAField'
88
import GroupsField from '../GroupsField'
99
import BillingAccountField from '../BillingAccountField'
@@ -18,11 +18,13 @@ class EditProjectDefaultsForm extends React.Component {
1818

1919
this.state = {
2020
enableButton: false,
21-
isLoading: true
21+
isLoading: true,
22+
isBillingAccountExpired: false,
2223
}
2324

2425
this.handleChange = this.handleChange.bind(this)
2526
this.handleSubmit = this.handleSubmit.bind(this)
27+
this.setBillingAccountExpired = this.setBillingAccountExpired.bind(this)
2628
}
2729

2830
componentDidMount() {
@@ -35,6 +37,11 @@ class EditProjectDefaultsForm extends React.Component {
3537
}
3638
}
3739

40+
setBillingAccountExpired(value) {
41+
this.setState({
42+
isBillingAccountExpired: value
43+
})
44+
}
3845
handleChange(changed) {
3946
const keys = _.intersection(Object.keys(changed), Object.keys(this.state.project))
4047
const reqProjectState = keys.reduce((acc, curr) => {
@@ -52,7 +59,7 @@ class EditProjectDefaultsForm extends React.Component {
5259
}
5360

5461
handleSubmit() {
55-
const {updateProject} = this.props
62+
const {updateProject, loadProjectBillingAccount} = this.props
5663
const {id} = this.props.project
5764
const updateProjectObj = Object.keys(this.state.project)
5865
.filter(key => !_.isEqual(this.props.project[key], this.state.project[key]))
@@ -61,7 +68,7 @@ class EditProjectDefaultsForm extends React.Component {
6168
return acc
6269
}, {})
6370
updateProject(id, updateProjectObj)
64-
.then(() => this.setState({enableButton: false}))
71+
.then(() => this.setState({enableButton: false})).then(() => loadProjectBillingAccount(id))
6572
.catch(console.error)
6673
}
6774

@@ -86,14 +93,16 @@ class EditProjectDefaultsForm extends React.Component {
8693
<BillingAccountField
8794
name="billingAccountId"
8895
projectId={this.state.project.id}
96+
isExpired={this.props.isBillingAccountExpired}
8997
value={this.state.project.billingAccountId}
98+
setBillingAccountExpired={this.setBillingAccountExpired}
9099
/>
91100
</div>
92101
<div className="section-footer section-footer-spec">
93102
<button
94103
className="tc-btn tc-btn-primary tc-btn-md"
95104
type="submit"
96-
disabled={!this.state.enableButton}
105+
disabled={this.state.isBillingAccountExpired || !this.state.enableButton}
97106
>
98107
Save
99108
</button>
@@ -105,7 +114,8 @@ class EditProjectDefaultsForm extends React.Component {
105114
}
106115

107116
const mapDispatchToProps = {
108-
updateProject
117+
updateProject,
118+
loadProjectBillingAccount
109119
}
110120

111121
export default protectComponent(

src/projects/detail/components/timeline/CreateMilestoneForm/CreateMilestoneForm.jsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ class CreateMilestoneForm extends React.Component {
3434
cancelEdit() {
3535
const { previousMilestone } = this.props
3636
const startDate = previousMilestone ? moment.utc(previousMilestone.completionDate || previousMilestone.endDate) : moment.utc()
37-
this.setState({
37+
this.state = {
3838
isEditing: false,
3939
type: '',
4040
name: '',
4141
startDate: startDate.format('YYYY-MM-DD'),
4242
endDate: startDate.add(3, 'days').format('YYYY-MM-DD')
43-
})
43+
}
4444
}
4545

4646
onAddClick() {

src/projects/detail/containers/ProjectDefaultsContainer.jsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class ProjectDefaultsContainer extends Component {
1818
isProcessing,
1919
feeds,
2020
isFeedsLoading,
21+
isBillingAccountExpired,
2122
productsTimelines,
2223
phasesTopics,
2324
location,
@@ -54,7 +55,7 @@ class ProjectDefaultsContainer extends Component {
5455
</MediaQuery>
5556
</TwoColsLayout.Sidebar>
5657
<TwoColsLayout.Content>
57-
<EditProjectDefaultsForm project={this.props.project} />
58+
<EditProjectDefaultsForm project={this.props.project} isBillingAccountExpired={isBillingAccountExpired} />
5859
</TwoColsLayout.Content>
5960
</TwoColsLayout>
6061
)
@@ -68,6 +69,7 @@ const mapStateToProps = ({ loadUser, projectState, projectTopics, topics }) => {
6869

6970
return {
7071
user: loadUser.user,
72+
isBillingAccountExpired: projectState.isBillingAccountExpired,
7173
isProcessing: projectState.processing,
7274
phases: projectState.phases,
7375
phasesNonDirty: projectState.phasesNonDirty,

src/projects/detail/containers/ProjectInfoContainer.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class ProjectInfoContainer extends React.Component {
8383
!_.isEqual(nextProps.feeds, this.props.feeds) ||
8484
!_.isEqual(nextProps.phases, this.props.phases) ||
8585
!_.isEqual(nextProps.productsTimelines, this.props.productsTimelines) ||
86+
!_.isEqual(nextProps.isBillingAccountExpired, this.props.isBillingAccountExpired) ||
8687
!_.isEqual(nextProps.phasesTopics, this.props.phasesTopics) ||
8788
!_.isEqual(nextProps.isFeedsLoading, this.props.isFeedsLoading) ||
8889
!_.isEqual(nextProps.isProjectProcessing, this.props.isProjectProcessing) ||
@@ -426,7 +427,7 @@ class ProjectInfoContainer extends React.Component {
426427
render() {
427428
const { showDeleteConfirm } = this.state
428429
const { project, phases, hideInfo, hideMembers,
429-
productsTimelines, isProjectProcessing, notifications, projectTemplates } = this.props
430+
productsTimelines, isProjectProcessing, notifications, projectTemplates, isBillingAccountExpired } = this.props
430431

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

459460
const renderFAQs = containsFAQ(projectTemplate)
460-
const navLinks = getProjectNavLinks(project, project.id, renderFAQs).map((navLink) => {
461+
const navLinks = getProjectNavLinks(project, project.id, renderFAQs, isBillingAccountExpired).map((navLink) => {
461462
if (navLink.label === 'Messages') {
462463
navLink.count = notReadMessageNotifications.length
463464
navLink.toolTipText = 'New messages'
@@ -593,10 +594,11 @@ ProjectInfoContainer.PropTypes = {
593594
canAccessPrivatePosts: PropTypes.bool.isRequired,
594595
}
595596

596-
const mapStateToProps = ({ templates, notifications }) => {
597+
const mapStateToProps = ({ templates, projectState, notifications }) => {
597598
const canAccessPrivatePosts = hasPermission(PERMISSIONS.ACCESS_PRIVATE_POST)
598599
return ({
599600
projectTemplates : templates.projectTemplates,
601+
isBillingAccountExpired: projectState.isBillingAccountExpired,
600602
canAccessPrivatePosts,
601603
notifications: notifications.notifications,
602604
})

src/projects/reducers/project.js

+12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_PENDING, CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_SUCCESS, CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_FAILURE,
33
LOAD_PROJECT_PENDING, LOAD_PROJECT_SUCCESS, LOAD_PROJECT_MEMBER_INVITE_PENDING, LOAD_PROJECT_MEMBER_INVITE_SUCCESS, LOAD_PROJECT_FAILURE,
4+
LOAD_PROJECT_BILLING_ACCOUNT_SUCCESS, LOAD_PROJECT_BILLING_ACCOUNT_FAILURE,
45
CREATE_PROJECT_PENDING, CREATE_PROJECT_SUCCESS, CREATE_PROJECT_FAILURE, CLEAR_LOADED_PROJECT,
56
UPDATE_PROJECT_PENDING, UPDATE_PROJECT_SUCCESS, UPDATE_PROJECT_FAILURE,
67
DELETE_PROJECT_PENDING, DELETE_PROJECT_SUCCESS, DELETE_PROJECT_FAILURE,
@@ -36,6 +37,7 @@ export function getEmptyProjectObject() {
3637

3738
const initialState = {
3839
isLoading: true,
40+
isBillingAccountExpired: false,
3941
processing: false,
4042
processingMembers: false,
4143
updatingMemberIds: [],
@@ -151,6 +153,16 @@ export const projectState = function (state=initialState, action) {
151153
return Object.assign({}, state, {
152154
isCreatingPhase: true
153155
})
156+
case LOAD_PROJECT_BILLING_ACCOUNT_SUCCESS:
157+
return {
158+
...state,
159+
isBillingAccountExpired: !action.payload.data.active,
160+
}
161+
case LOAD_PROJECT_BILLING_ACCOUNT_FAILURE:
162+
return {
163+
...state,
164+
isBillingAccountExpired: false,
165+
}
154166
case CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_SUCCESS:
155167
case CREATE_PROJECT_PHASE_SUCCESS: {
156168
const { phase, product } = action.payload

0 commit comments

Comments
 (0)