mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-06 21:24:23 +08:00
Merge pull request #792 from Ducz0r/lm-sci-1501
[SCI-1501] Rewrite "New team" wizard into React.js
This commit is contained in:
commit
a399b1dd44
17 changed files with 430 additions and 56 deletions
|
@ -4,59 +4,93 @@ module ClientApi
|
||||||
include ClientApi::Users::UserTeamsHelper
|
include ClientApi::Users::UserTeamsHelper
|
||||||
|
|
||||||
def index
|
def index
|
||||||
success_response('/client_api/teams/index',
|
success_response(template: '/client_api/teams/index',
|
||||||
teams: current_user.teams_data)
|
locals: { teams: current_user.teams_data })
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
service = ClientApi::Teams::CreateService.new(
|
||||||
|
current_user: current_user,
|
||||||
|
params: team_params
|
||||||
|
)
|
||||||
|
result = service.execute
|
||||||
|
|
||||||
|
if result[:status] == :success
|
||||||
|
success_response(details: { id: service.team.id })
|
||||||
|
else
|
||||||
|
error_response(
|
||||||
|
message: result[:message],
|
||||||
|
details: service.team.errors
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def details
|
def details
|
||||||
team_service = ClientApi::TeamsService.new(team_id: params[:team_id],
|
teams_service = ClientApi::TeamsService.new(team_id: params[:team_id],
|
||||||
current_user: current_user)
|
current_user: current_user)
|
||||||
success_response('/client_api/teams/details',
|
success_response(template: '/client_api/teams/details',
|
||||||
team_service.team_page_details_data)
|
locals: teams_service.team_page_details_data)
|
||||||
rescue ClientApi::CustomTeamError
|
rescue ClientApi::CustomTeamError
|
||||||
error_response
|
error_response
|
||||||
end
|
end
|
||||||
|
|
||||||
def change_team
|
def change_team
|
||||||
team_service = ClientApi::TeamsService.new(team_id: params[:team_id],
|
teams_service = ClientApi::TeamsService.new(team_id: params[:team_id],
|
||||||
current_user: current_user)
|
current_user: current_user)
|
||||||
team_service.change_current_team!
|
teams_service.change_current_team!
|
||||||
success_response('/client_api/teams/index', team_service.teams_data)
|
success_response(template: '/client_api/teams/index',
|
||||||
|
locals: teams_service.teams_data)
|
||||||
rescue ClientApi::CustomTeamError
|
rescue ClientApi::CustomTeamError
|
||||||
error_response
|
error_response
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
team_service = ClientApi::TeamsService.new(team_id: params[:team_id],
|
teams_service = ClientApi::TeamsService.new(team_id: params[:team_id],
|
||||||
current_user: current_user,
|
current_user: current_user,
|
||||||
params: team_params)
|
params: team_params)
|
||||||
team_service.update_team!
|
teams_service.update_team!
|
||||||
success_response('/client_api/teams/update_details',
|
success_response(template: '/client_api/teams/update_details',
|
||||||
team_service.single_team_details_data)
|
locals: teams_service.single_team_details_data)
|
||||||
rescue ClientApi::CustomTeamError => error
|
rescue ClientApi::CustomTeamError => error
|
||||||
error_response(error.to_s)
|
error_response(message: error.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def team_params
|
def team_params
|
||||||
params.require(:team).permit(:description, :name)
|
params.require(:team).permit(:name, :description)
|
||||||
end
|
end
|
||||||
|
|
||||||
def success_response(template, locals)
|
def success_response(args = {})
|
||||||
|
template = args.fetch(:template) { nil }
|
||||||
|
locals = args.fetch(:locals) { {} }
|
||||||
|
details = args.fetch(:details) { {} }
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.json do
|
format.json do
|
||||||
render template: template,
|
if template
|
||||||
status: :ok,
|
render template: template,
|
||||||
locals: locals
|
status: :ok,
|
||||||
|
locals: locals
|
||||||
|
else
|
||||||
|
render json: { details: details }, status: :ok
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def error_response(message = t('client_api.generic_error_message'))
|
def error_response(args = {})
|
||||||
|
message = args.fetch(:message) { t('client_api.generic_error_message') }
|
||||||
|
details = args.fetch(:details) { {} }
|
||||||
|
status = args.fetch(:status) { :unprocessable_entity }
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.json do
|
format.json do
|
||||||
render json: { message: message }, status: :unprocessable_entity
|
render json: {
|
||||||
|
message: message,
|
||||||
|
details: details
|
||||||
|
},
|
||||||
|
status: status
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,6 +11,7 @@ export const SETTINGS_ACCOUNT_PREFERENCES_PATH =
|
||||||
|
|
||||||
// teams
|
// teams
|
||||||
export const TEAMS_PATH = "/client_api/teams";
|
export const TEAMS_PATH = "/client_api/teams";
|
||||||
|
export const TEAMS_NEW_PATH = "/client_api/teams";
|
||||||
export const CHANGE_TEAM_PATH = "/client_api/teams/change_team";
|
export const CHANGE_TEAM_PATH = "/client_api/teams/change_team";
|
||||||
export const TEAM_DETAILS_PATH = "/client_api/teams/:team_id/details";
|
export const TEAM_DETAILS_PATH = "/client_api/teams/:team_id/details";
|
||||||
export const TEAM_UPDATE_PATH = "/client_api/teams/update";
|
export const TEAM_UPDATE_PATH = "/client_api/teams/update";
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export const ENTER_KEY_CODE = 13;
|
export const ENTER_KEY_CODE = 13;
|
||||||
export const TEXT_MAX_LENGTH = 10000;
|
export const NAME_MIN_LENGTH = 2;
|
||||||
export const NAME_MAX_LENGTH = 255;
|
export const NAME_MAX_LENGTH = 255;
|
||||||
|
export const TEXT_MAX_LENGTH = 10000;
|
||||||
|
|
|
@ -8,6 +8,7 @@ export default {
|
||||||
loading: "Loading ..."
|
loading: "Loading ..."
|
||||||
},
|
},
|
||||||
error_messages: {
|
error_messages: {
|
||||||
|
text_too_short: "is too short (minimum is {min_length} characters)",
|
||||||
text_too_long: "is too long (maximum is {max_length} characters)"
|
text_too_long: "is too long (maximum is {max_length} characters)"
|
||||||
},
|
},
|
||||||
navbar: {
|
navbar: {
|
||||||
|
@ -101,6 +102,15 @@ export default {
|
||||||
administrator: "Administrator",
|
administrator: "Administrator",
|
||||||
remove_user: "Remove"
|
remove_user: "Remove"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
new_team: {
|
||||||
|
title: "New team",
|
||||||
|
name_label: "Team name",
|
||||||
|
name_placeholder: "My team",
|
||||||
|
name_sublabel: "Pick a name that would best describe your team (e.g. 'University of ..., Department of ...').",
|
||||||
|
description_label: "Description",
|
||||||
|
description_sublabel: "Describe your team.",
|
||||||
|
create: "Create team"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
activities: {
|
activities: {
|
||||||
|
|
|
@ -4,5 +4,6 @@ export const ROOT_PATH = "/";
|
||||||
// Settings page
|
// Settings page
|
||||||
export const SETTINGS_TEAMS_ROUTE = "/settings/teams";
|
export const SETTINGS_TEAMS_ROUTE = "/settings/teams";
|
||||||
export const SETTINGS_TEAM_ROUTE = "/settings/teams/:id";
|
export const SETTINGS_TEAM_ROUTE = "/settings/teams/:id";
|
||||||
|
export const SETTINGS_NEW_TEAM_ROUTE = "/settings/teams/new";
|
||||||
export const SETTINGS_ACCOUNT_PROFILE = "/settings/account/profile";
|
export const SETTINGS_ACCOUNT_PROFILE = "/settings/account/profile";
|
||||||
export const SETTINGS_ACCOUNT_PREFERENCES = "/settings/account/preferences";
|
export const SETTINGS_ACCOUNT_PREFERENCES = "/settings/account/preferences";
|
|
@ -8,7 +8,8 @@ import {
|
||||||
ROOT_PATH,
|
ROOT_PATH,
|
||||||
SETTINGS_TEAMS_ROUTE,
|
SETTINGS_TEAMS_ROUTE,
|
||||||
SETTINGS_TEAM_ROUTE,
|
SETTINGS_TEAM_ROUTE,
|
||||||
SETTINGS_ACCOUNT_PROFILE
|
SETTINGS_ACCOUNT_PROFILE,
|
||||||
|
SETTINGS_NEW_TEAM_ROUTE
|
||||||
} from "../../config/routes";
|
} from "../../config/routes";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -21,6 +22,7 @@ import NotFound from "../../components/404/NotFound";
|
||||||
import SettingsAccount from "./scenes/account/SettingsAccount";
|
import SettingsAccount from "./scenes/account/SettingsAccount";
|
||||||
import SettingsTeams from "./scenes/teams";
|
import SettingsTeams from "./scenes/teams";
|
||||||
import SettingsTeam from "./scenes/team";
|
import SettingsTeam from "./scenes/team";
|
||||||
|
import SettingsNewTeam from "./scenes/teams/new";
|
||||||
|
|
||||||
export default class SettingsPage extends Component {
|
export default class SettingsPage extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -70,6 +72,7 @@ export default class SettingsPage extends Component {
|
||||||
component={SettingsAccount}
|
component={SettingsAccount}
|
||||||
/>}
|
/>}
|
||||||
/>
|
/>
|
||||||
|
<Route path={SETTINGS_NEW_TEAM_ROUTE} component={SettingsNewTeam} />
|
||||||
<Route path={SETTINGS_TEAM_ROUTE} component={SettingsTeam} />
|
<Route path={SETTINGS_TEAM_ROUTE} component={SettingsTeam} />
|
||||||
<Route path={SETTINGS_TEAMS_ROUTE} component={SettingsTeams} />
|
<Route path={SETTINGS_TEAMS_ROUTE} component={SettingsTeams} />
|
||||||
<Route
|
<Route
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import ReactRouterPropTypes from "react-router-prop-types";
|
import ReactRouterPropTypes from "react-router-prop-types";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { Row, Col, Glyphicon, Well } from "react-bootstrap";
|
import { Breadcrumb, Row, Col, Glyphicon, Well } from "react-bootstrap";
|
||||||
|
import { LinkContainer } from "react-router-bootstrap";
|
||||||
import { FormattedHTMLMessage, FormattedMessage } from "react-intl";
|
import { FormattedHTMLMessage, FormattedMessage } from "react-intl";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import prettysize from "prettysize";
|
import prettysize from "prettysize";
|
||||||
import axios from "../../../../config/axios";
|
import axios from "../../../../config/axios";
|
||||||
|
|
||||||
import { TEAM_DETAILS_PATH, SETTINGS_TEAMS } from "../../../../config/api_endpoints";
|
import { SETTINGS_TEAMS_ROUTE } from "../../../../config/routes";
|
||||||
|
import { TEAM_DETAILS_PATH } from "../../../../config/api_endpoints";
|
||||||
import { BORDER_LIGHT_COLOR } from "../../../../config/constants/colors";
|
import { BORDER_LIGHT_COLOR } from "../../../../config/constants/colors";
|
||||||
|
|
||||||
import TeamsMembers from "./components/TeamsMembers";
|
import TeamsMembers from "./components/TeamsMembers";
|
||||||
|
@ -145,16 +146,16 @@ class SettingsTeam extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<StyledOl className="breadcrumb">
|
<Breadcrumb>
|
||||||
<li>
|
<LinkContainer to={SETTINGS_TEAMS_ROUTE}>
|
||||||
<Link to={SETTINGS_TEAMS}>
|
<Breadcrumb.Item>
|
||||||
<FormattedMessage id="settings_page.all_teams" />
|
<FormattedMessage id="settings_page.all_teams" />
|
||||||
</Link>
|
</Breadcrumb.Item>
|
||||||
</li>
|
</LinkContainer>
|
||||||
<li className="active">
|
<Breadcrumb.Item active={true}>
|
||||||
{this.state.team.name}
|
{this.state.team.name}
|
||||||
</li>
|
</Breadcrumb.Item>
|
||||||
</StyledOl>
|
</Breadcrumb>
|
||||||
<TabTitle>
|
<TabTitle>
|
||||||
<StyledH3 onClick={this.showNameModal}>
|
<StyledH3 onClick={this.showNameModal}>
|
||||||
{this.state.team.name}
|
{this.state.team.name}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { LinkContainer } from "react-router-bootstrap";
|
||||||
import PropTypes, { number, string, bool } from "prop-types";
|
import PropTypes, { number, string, bool } from "prop-types";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { FormattedMessage, FormattedPlural } from "react-intl";
|
import { FormattedMessage, FormattedPlural } from "react-intl";
|
||||||
import { Button, Glyphicon } from "react-bootstrap";
|
import { Button, Glyphicon } from "react-bootstrap";
|
||||||
|
import { SETTINGS_NEW_TEAM_ROUTE } from "../../../../../config/routes";
|
||||||
|
|
||||||
const Wrapper = styled.div`margin: 15px 0;`;
|
const Wrapper = styled.div`margin: 15px 0;`;
|
||||||
const TeamsPageDetails = ({ teams }) => {
|
const TeamsPageDetails = ({ teams }) => {
|
||||||
|
@ -28,13 +30,11 @@ const TeamsPageDetails = ({ teams }) => {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Button
|
<LinkContainer to={SETTINGS_NEW_TEAM_ROUTE}>
|
||||||
onClick={() => {
|
<Button>
|
||||||
window.location = "/users/settings/teams/new";
|
<Glyphicon glyph="plus" /> <FormattedMessage id="global_team_switch.new_team" />
|
||||||
}}
|
</Button>
|
||||||
>
|
</LinkContainer>
|
||||||
<Glyphicon glyph="plus" /> <FormattedMessage id="global_team_switch.new_team" />
|
|
||||||
</Button>
|
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import PropTypes, { number, string, bool } from "prop-types";
|
import PropTypes, { number, string, bool } from "prop-types";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
import { Breadcrumb } from "react-bootstrap";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import {
|
import { BORDER_LIGHT_COLOR } from "../../../../config/constants/colors";
|
||||||
BORDER_LIGHT_COLOR,
|
|
||||||
COLOR_CONCRETE
|
|
||||||
} from "../../../../config/constants/colors";
|
|
||||||
|
|
||||||
import TeamsPageDetails from "./components/TeamsPageDetails";
|
import TeamsPageDetails from "./components/TeamsPageDetails";
|
||||||
import TeamsDataTable from "./components/TeamsDataTable";
|
import TeamsDataTable from "./components/TeamsDataTable";
|
||||||
|
@ -21,16 +19,13 @@ const Wrapper = styled.div`
|
||||||
padding: 16px 15px 50px 15px;
|
padding: 16px 15px 50px 15px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TabTitle = styled.div`
|
|
||||||
background-color: ${COLOR_CONCRETE};
|
|
||||||
padding: 15px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SettingsTeams = ({ teams }) =>
|
const SettingsTeams = ({ teams }) =>
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<TabTitle>
|
<Breadcrumb>
|
||||||
<FormattedMessage id="settings_page.all_teams" />
|
<Breadcrumb.Item active={false}>
|
||||||
</TabTitle>
|
<FormattedMessage id="settings_page.all_teams" />
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
</Breadcrumb>
|
||||||
<TeamsPageDetails teams={teams} />
|
<TeamsPageDetails teams={teams} />
|
||||||
<TeamsDataTable teams={teams} />
|
<TeamsDataTable teams={teams} />
|
||||||
</Wrapper>;
|
</Wrapper>;
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import React from "react";
|
||||||
|
import {defineMessages, injectIntl, intlShape} from 'react-intl';
|
||||||
|
import { FormControl } from "react-bootstrap";
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
placeholder: { id: "settings_page.new_team.name_placeholder" }
|
||||||
|
});
|
||||||
|
|
||||||
|
const NameFormControl = ({ intl, ...props }) =>
|
||||||
|
<FormControl
|
||||||
|
type="text"
|
||||||
|
placeholder={intl.formatMessage(messages.placeholder)}
|
||||||
|
autoFocus={true}
|
||||||
|
{...props}
|
||||||
|
/>;
|
||||||
|
|
||||||
|
NameFormControl.propTypes = {
|
||||||
|
intl: intlShape.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default injectIntl(NameFormControl);
|
|
@ -0,0 +1,244 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { Breadcrumb, FormGroup, FormControl, ControlLabel, HelpBlock, Button } from "react-bootstrap";
|
||||||
|
import { Redirect } from "react-router";
|
||||||
|
import { LinkContainer } from "react-router-bootstrap";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
import update from "immutability-helper";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import _ from "lodash";
|
||||||
|
import axios from "../../../../../config/axios";
|
||||||
|
import {
|
||||||
|
SETTINGS_TEAMS_ROUTE,
|
||||||
|
SETTINGS_TEAM_ROUTE
|
||||||
|
} from "../../../../../config/routes";
|
||||||
|
import { TEAMS_NEW_PATH } from "../../../../../config/api_endpoints";
|
||||||
|
import {
|
||||||
|
NAME_MIN_LENGTH,
|
||||||
|
NAME_MAX_LENGTH,
|
||||||
|
TEXT_MAX_LENGTH
|
||||||
|
} from "../../../../../config/constants/numeric";
|
||||||
|
|
||||||
|
import { BORDER_LIGHT_COLOR } from "../../../../../config/constants/colors";
|
||||||
|
|
||||||
|
import NameFormControl from "./components/NameFormControl";
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
background: white;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid ${BORDER_LIGHT_COLOR};
|
||||||
|
border-top: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 16px 15px 50px 15px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MyFormGroupDiv = styled.div`
|
||||||
|
margin-bottom: 15px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
class SettingsNewTeam extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
team: {
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
},
|
||||||
|
formErrors: {
|
||||||
|
name: "",
|
||||||
|
description: ""
|
||||||
|
},
|
||||||
|
redirectTo: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onSubmit = this.onSubmit.bind(this);
|
||||||
|
this.validateField = this.validateField.bind(this);
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
this.renderTeamNameFormGroup = this.renderTeamNameFormGroup.bind(this);
|
||||||
|
this.renderTeamDescriptionFormGroup = this.renderTeamDescriptionFormGroup.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
axios({
|
||||||
|
method: "post",
|
||||||
|
url: TEAMS_NEW_PATH,
|
||||||
|
withCredentials: true,
|
||||||
|
data: { team: this.state.team }
|
||||||
|
})
|
||||||
|
.then(sr => {
|
||||||
|
// Redirect to the new team page
|
||||||
|
this.newState = { ...this.state };
|
||||||
|
this.newState = update(
|
||||||
|
this.newState,
|
||||||
|
{ redirectTo: {
|
||||||
|
$set: SETTINGS_TEAM_ROUTE.replace(':id', sr.data.details.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.setState(this.newState);
|
||||||
|
})
|
||||||
|
.catch(er => {
|
||||||
|
// Display errors
|
||||||
|
this.newState = { ...this.state };
|
||||||
|
['name', 'description'].forEach((el) => {
|
||||||
|
if (er.response.data.details[el]) {
|
||||||
|
this.newState = update(
|
||||||
|
this.newState,
|
||||||
|
{ formErrors: { name: { $set: <span>{er.response.data.details[el]}</span> } } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.setState(this.newState);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validateField(key, value) {
|
||||||
|
let errorMessage;
|
||||||
|
if (key === "name") {
|
||||||
|
errorMessage = "";
|
||||||
|
|
||||||
|
if (value.length < NAME_MIN_LENGTH) {
|
||||||
|
errorMessage = <FormattedMessage id="error_messages.text_too_short" values={{ min_length: NAME_MIN_LENGTH }} />;
|
||||||
|
} else if (value.length > NAME_MAX_LENGTH) {
|
||||||
|
errorMessage = <FormattedMessage id="error_messages.text_too_long" values={{ max_length: NAME_MAX_LENGTH }} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.newState = update(
|
||||||
|
this.newState,
|
||||||
|
{ formErrors: { name: { $set: errorMessage } } }
|
||||||
|
);
|
||||||
|
} else if (key === "description") {
|
||||||
|
errorMessage = "";
|
||||||
|
|
||||||
|
if (value.length > TEXT_MAX_LENGTH) {
|
||||||
|
errorMessage = <FormattedMessage id="error_messages.text_too_long" values={{ max_length: TEXT_MAX_LENGTH }} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.newState = update(
|
||||||
|
this.newState,
|
||||||
|
{ formErrors: { description: { $set: errorMessage } } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(e) {
|
||||||
|
const key = e.target.name;
|
||||||
|
const value = e.target.value;
|
||||||
|
|
||||||
|
this.newState = { ...this.state };
|
||||||
|
|
||||||
|
// Update value in the state
|
||||||
|
this.newState = update(
|
||||||
|
this.newState,
|
||||||
|
{ team: { [key]: { $set: value } } }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Validate the input
|
||||||
|
this.validateField(key, value);
|
||||||
|
|
||||||
|
// Refresh state
|
||||||
|
this.setState(this.newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTeamNameFormGroup() {
|
||||||
|
const formGroupClass = this.state.formErrors.name ? "form-group has-error" : "form-group";
|
||||||
|
const validationState = this.state.formErrors.name ? "error" : null;
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
controlId="formTeamName"
|
||||||
|
className={formGroupClass}
|
||||||
|
validationState={validationState}
|
||||||
|
>
|
||||||
|
<ControlLabel>
|
||||||
|
<FormattedMessage id="settings_page.new_team.name_label" />
|
||||||
|
</ControlLabel>
|
||||||
|
<NameFormControl
|
||||||
|
value={this.state.team.name}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
name="name"
|
||||||
|
/>
|
||||||
|
<FormControl.Feedback />
|
||||||
|
<HelpBlock>{this.state.formErrors.name}</HelpBlock>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTeamDescriptionFormGroup() {
|
||||||
|
const formGroupClass = this.state.formErrors.description ? "form-group has-error" : "form-group";
|
||||||
|
const validationState = this.state.formErrors.description ? "error" : null;
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
controlId="formTeamDescription"
|
||||||
|
className={formGroupClass}
|
||||||
|
validationState={validationState}
|
||||||
|
>
|
||||||
|
<ControlLabel>
|
||||||
|
<FormattedMessage id="settings_page.new_team.description_label" />
|
||||||
|
</ControlLabel>
|
||||||
|
<FormControl
|
||||||
|
componentClass="textarea"
|
||||||
|
value={this.state.team.description}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
name="description"
|
||||||
|
/>
|
||||||
|
<FormControl.Feedback />
|
||||||
|
<HelpBlock>{this.state.formErrors.description}</HelpBlock>
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
// Redirect if form successful
|
||||||
|
if (!_.isEmpty(this.state.redirectTo)) {
|
||||||
|
return <Redirect to={this.state.redirectTo} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const btnDisabled = !_.isEmpty(this.state.formErrors.name) ||
|
||||||
|
!_.isEmpty(this.state.formErrors.description);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<Breadcrumb>
|
||||||
|
<LinkContainer to={SETTINGS_TEAMS_ROUTE}>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<FormattedMessage id="settings_page.all_teams" />
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
</LinkContainer>
|
||||||
|
<Breadcrumb.Item active={true}>
|
||||||
|
<FormattedMessage id="settings_page.new_team.title" />
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
</Breadcrumb>
|
||||||
|
|
||||||
|
<form onSubmit={this.onSubmit} style={{ maxWidth: "500px" }}>
|
||||||
|
|
||||||
|
<MyFormGroupDiv>
|
||||||
|
{this.renderTeamNameFormGroup()}
|
||||||
|
<small>
|
||||||
|
<FormattedMessage id="settings_page.new_team.name_sublabel" />
|
||||||
|
</small>
|
||||||
|
</MyFormGroupDiv>
|
||||||
|
|
||||||
|
<MyFormGroupDiv>
|
||||||
|
{this.renderTeamDescriptionFormGroup()}
|
||||||
|
<small>
|
||||||
|
<FormattedMessage id="settings_page.new_team.description_sublabel" />
|
||||||
|
</small>
|
||||||
|
</MyFormGroupDiv>
|
||||||
|
|
||||||
|
<LinkContainer to={SETTINGS_TEAMS_ROUTE}>
|
||||||
|
<Button>
|
||||||
|
<FormattedMessage id="general.cancel" />
|
||||||
|
</Button>
|
||||||
|
</LinkContainer>
|
||||||
|
<Button type="submit" className="btn-primary" disabled={btnDisabled}>
|
||||||
|
<FormattedMessage id="settings_page.new_team.create" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettingsNewTeam;
|
27
app/services/client_api/base_service.rb
Normal file
27
app/services/client_api/base_service.rb
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
module ClientApi
|
||||||
|
class BaseService
|
||||||
|
attr_accessor :current_user, :params
|
||||||
|
|
||||||
|
def initialize(args)
|
||||||
|
@current_user = args.fetch(:current_user) { raise StandardError }
|
||||||
|
@params = (args.fetch(:params) { {} }).dup
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def error(message, http_status = nil)
|
||||||
|
result = {
|
||||||
|
message: message,
|
||||||
|
status: :error
|
||||||
|
}
|
||||||
|
|
||||||
|
result[:http_status] = http_status if http_status
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
def success(pass_back = {})
|
||||||
|
pass_back[:status] = :success
|
||||||
|
pass_back
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
26
app/services/client_api/teams/create_service.rb
Normal file
26
app/services/client_api/teams/create_service.rb
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
module ClientApi
|
||||||
|
module Teams
|
||||||
|
class CreateService < BaseService
|
||||||
|
attr_accessor :team
|
||||||
|
|
||||||
|
def execute
|
||||||
|
@team = Team.new(@params)
|
||||||
|
@team.created_by = @current_user
|
||||||
|
|
||||||
|
if @team.save
|
||||||
|
# Okay, team is created, now
|
||||||
|
# add the current user as admin
|
||||||
|
UserTeam.create(
|
||||||
|
user: @current_user,
|
||||||
|
team: @team,
|
||||||
|
role: 2
|
||||||
|
)
|
||||||
|
|
||||||
|
success
|
||||||
|
else
|
||||||
|
error(@team.errors.full_messages.uniq.join('. '))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,10 +1,10 @@
|
||||||
module ClientApi
|
module ClientApi
|
||||||
class TeamsService
|
class TeamsService
|
||||||
def initialize(arg)
|
def initialize(arg)
|
||||||
team_id = arg.fetch(:team_id) { raise ClientApi::CustomTeamError }
|
|
||||||
@params = arg.fetch(:params) { false }
|
@params = arg.fetch(:params) { false }
|
||||||
@team = Team.find_by_id(team_id)
|
|
||||||
@user = arg.fetch(:current_user) { raise ClientApi::CustomTeamError }
|
@user = arg.fetch(:current_user) { raise ClientApi::CustomTeamError }
|
||||||
|
team_id = arg.fetch(:team_id) { raise ClientApi::CustomTeamError }
|
||||||
|
@team = Team.find_by_id(team_id)
|
||||||
raise ClientApi::CustomTeamError unless @user.teams.include? @team
|
raise ClientApi::CustomTeamError unless @user.teams.include? @team
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -34,5 +34,6 @@ module ClientApi
|
||||||
{ teams: @user.teams_data }
|
{ teams: @user.teams_data }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
CustomTeamError = Class.new(StandardError)
|
CustomTeamError = Class.new(StandardError)
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,7 +19,9 @@ Rails.application.routes.draw do
|
||||||
# teams
|
# teams
|
||||||
get '/teams', to: 'teams/teams#index'
|
get '/teams', to: 'teams/teams#index'
|
||||||
namespace :teams do
|
namespace :teams do
|
||||||
|
get '/new', to: 'teams#new'
|
||||||
get '/:team_id/details', to: 'teams#details'
|
get '/:team_id/details', to: 'teams#details'
|
||||||
|
post '/', to: 'teams#create'
|
||||||
post '/change_team', to: 'teams#change_team'
|
post '/change_team', to: 'teams#change_team'
|
||||||
post '/update', to: 'teams#update'
|
post '/update', to: 'teams#update'
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
"extract-text-webpack-plugin": "^3.0.0",
|
"extract-text-webpack-plugin": "^3.0.0",
|
||||||
"file-loader": "^0.11.2",
|
"file-loader": "^0.11.2",
|
||||||
"glob": "^7.1.2",
|
"glob": "^7.1.2",
|
||||||
|
"immutability-helper": "^2.3.0",
|
||||||
"js-yaml": "^3.9.0",
|
"js-yaml": "^3.9.0",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
"moment": "^2.18.1",
|
"moment": "^2.18.1",
|
||||||
|
|
|
@ -2756,6 +2756,12 @@ ignore@^3.2.0:
|
||||||
version "3.3.3"
|
version "3.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d"
|
||||||
|
|
||||||
|
immutability-helper@^2.3.0:
|
||||||
|
version "2.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-2.3.1.tgz#8ccfce92157208c120b2afad7ed05c11114c086e"
|
||||||
|
dependencies:
|
||||||
|
invariant "^2.2.0"
|
||||||
|
|
||||||
imurmurhash@^0.1.4:
|
imurmurhash@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||||
|
@ -2845,7 +2851,7 @@ intl-relativeformat@^1.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
intl-messageformat "1.3.0"
|
intl-messageformat "1.3.0"
|
||||||
|
|
||||||
invariant@^2.0.0, invariant@^2.1.0, invariant@^2.1.1, invariant@^2.2.1, invariant@^2.2.2:
|
invariant@^2.0.0, invariant@^2.1.0, invariant@^2.1.1, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
|
||||||
version "2.2.2"
|
version "2.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
|
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Add table
Reference in a new issue