From a298f8fe28f83f0b08b7481d85792057f9881033 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Mon, 6 Feb 2017 17:45:29 +0100 Subject: [PATCH] SONAR-8666 Make it possible to create a new organization --- .../src/main/js/api/organizations.js | 15 +- .../js/apps/account/components/Account.js | 15 +- .../main/js/apps/account/components/Nav.js | 26 +- .../organizations/CreateOrganizationForm.js | 240 ++++++++++++++++++ .../organizations/UserOrganizations.js | 48 ++++ .../src/main/js/apps/account/routes.js | 5 + .../src/main/js/apps/organizations/actions.js | 17 +- .../resources/org/sonar/l10n/core.properties | 5 + 8 files changed, 349 insertions(+), 22 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/account/organizations/CreateOrganizationForm.js create mode 100644 server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.js diff --git a/server/sonar-web/src/main/js/api/organizations.js b/server/sonar-web/src/main/js/api/organizations.js index 4685c4ee2c9..ef998da81c3 100644 --- a/server/sonar-web/src/main/js/api/organizations.js +++ b/server/sonar-web/src/main/js/api/organizations.js @@ -18,7 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // @flow -import { getJSON, post } from '../helpers/request'; +import { getJSON, post, postJSON } from '../helpers/request'; +import type { Organization } from '../store/organizations/duck'; export const getOrganizations = (organizations?: Array) => { const data = {}; @@ -28,13 +29,7 @@ export const getOrganizations = (organizations?: Array) => { return getJSON('/api/organizations/search', data); }; -type GetOrganizationType = null | { - avatar?: string, - description?: string, - key: string, - name: string, - url?: string -}; +type GetOrganizationType = null | Organization; type GetOrganizationNavigation = { canAdmin: boolean, @@ -49,6 +44,10 @@ export const getOrganizationNavigation = (key: string): Promise r.organization); }; +export const createOrganization = (fields: {}): Promise => ( + postJSON('/api/organizations/create', fields).then(r => r.organization) +); + export const updateOrganization = (key: string, changes: {}) => ( post('/api/organizations/update', { key, ...changes }) ); diff --git a/server/sonar-web/src/main/js/apps/account/components/Account.js b/server/sonar-web/src/main/js/apps/account/components/Account.js index ec2f895d076..e87d7911149 100644 --- a/server/sonar-web/src/main/js/apps/account/components/Account.js +++ b/server/sonar-web/src/main/js/apps/account/components/Account.js @@ -21,7 +21,7 @@ import React from 'react'; import { connect } from 'react-redux'; import Nav from './Nav'; import UserCard from './UserCard'; -import { getCurrentUser } from '../../../store/rootReducer'; +import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer'; import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; import '../account.css'; @@ -44,7 +44,7 @@ class Account extends React.Component {
-
@@ -54,8 +54,9 @@ class Account extends React.Component { } } -export default connect( - state => ({ - currentUser: getCurrentUser(state) - }) -)(Account); +const mapStateToProps = state => ({ + currentUser: getCurrentUser(state), + customOrganizations: areThereCustomOrganizations(state) +}); + +export default connect(mapStateToProps)(Account); diff --git a/server/sonar-web/src/main/js/apps/account/components/Nav.js b/server/sonar-web/src/main/js/apps/account/components/Nav.js index f02b4f2db89..2889e7c259e 100644 --- a/server/sonar-web/src/main/js/apps/account/components/Nav.js +++ b/server/sonar-web/src/main/js/apps/account/components/Nav.js @@ -17,11 +17,16 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { Link, IndexLink } from 'react-router'; import { translate } from '../../../helpers/l10n'; -const Nav = () => ( +type Props = { + customOrganizations: boolean +}; + +const Nav = ({ customOrganizations }: Props) => ( ); diff --git a/server/sonar-web/src/main/js/apps/account/organizations/CreateOrganizationForm.js b/server/sonar-web/src/main/js/apps/account/organizations/CreateOrganizationForm.js new file mode 100644 index 00000000000..4196449ad0f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/account/organizations/CreateOrganizationForm.js @@ -0,0 +1,240 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +// @flow +import React from 'react'; +import Modal from 'react-modal'; +import debounce from 'lodash/debounce'; +import { connect } from 'react-redux'; +import { withRouter } from 'react-router'; +import { translate } from '../../../helpers/l10n'; +import { createOrganization } from '../../organizations/actions'; + +type State = { + loading: boolean, + avatar: string, + avatarImage: string, + description: string, + key: string, + name: string, + url: string +}; + +class CreateOrganizationForm extends React.Component { + mounted: boolean; + + props: { + createOrganization: () => Promise<*>, + router: { push: (string) => void } + }; + + state: State = { + loading: false, + avatar: '', + avatarImage: '', + description: '', + key: '', + name: '', + url: '' + }; + + constructor (props) { + super(props); + this.changeAvatarImage = debounce(this.changeAvatarImage, 500); + } + + componentDidMount () { + this.mounted = true; + } + + componentWillUnmount () { + this.mounted = false; + } + + closeForm = () => { + this.props.router.push('/account/organizations'); + }; + + stopProcessing = () => { + if (this.mounted) { + this.setState({ loading: false }); + } + }; + + stopProcessingAndClose = () => { + if (this.mounted) { + this.setState({ loading: false }); + } + this.closeForm(); + }; + + handleAvatarInputChange = (e: Object) => { + const { value } = e.target; + this.setState({ avatar: value }); + this.changeAvatarImage(value); + }; + + changeAvatarImage = (value: string) => { + this.setState({ avatarImage: value }); + }; + + handleSubmit = (e: Object) => { + e.preventDefault(); + const organization: Object = { name: this.state.name }; + if (this.state.avatar) { + Object.assign(organization, { avatar: this.state.avatar }); + } + if (this.state.description) { + Object.assign(organization, { description: this.state.description }); + } + if (this.state.key) { + Object.assign(organization, { key: this.state.key }); + } + if (this.state.url) { + Object.assign(organization, { url: this.state.url }); + } + this.setState({ loading: true }); + this.props.createOrganization(organization).then(this.stopProcessingAndClose, this.stopProcessing); + }; + + render () { + return ( + +
+

{translate('my_account.create_organization')}

+
+ +
+
+
+ + this.setState({ key: e.target.value })}/> +
+ {translate('organization.key.description')} +
+
+
+ + this.setState({ name: e.target.value })}/> +
+ {translate('organization.name.description')} +
+
+
+ + +
+ {translate('organization.avatar.description')} +
+ {!!this.state.avatarImage && ( +
+
+ {translate('organization.avatar.preview')} + {':'} +
+ +
+ )} +
+
+ +