fix hound and refactor

This commit is contained in:
mlorb 2017-09-21 10:36:09 +02:00
parent 48c8922604
commit 55772a80ab
12 changed files with 385 additions and 325 deletions

View file

@ -1,34 +1,34 @@
module ClientApi module ClientApi
module Users module Users
class InvitationsController < Devise::InvitationsController class InvitationsController < Devise::InvitationsController
before_action :check_invite_users_permission, only: :invite_users
before_action :check_invite_users_permission, only: :invite_users def invite_users
invite_service =
ClientApi::InvitationsService.new(user: current_user,
team: @team,
role: params['user_role'],
emails: params[:emails])
invite_results = invite_service.invitation
success_response(invite_results)
end
def invite_users def success_response(invite_results)
invite_service = ClientApi::InvitationsService.new(user: current_user, respond_to do |format|
team: @team, format.json do
role: params['user_role'], render template: '/client_api/users/invite_users',
emails: params[:emails]) status: :ok,
invite_results = invite_service.invitation locals: { invite_results: invite_results, team: @team }
success_response(invite_results) end
end
def success_response(invite_results)
respond_to do |format|
format.json do
render template: '/client_api/users/invite_users',
status: :ok,
locals: {invite_results: invite_results, team: @team}
end end
end end
end
private private
def check_invite_users_permission def check_invite_users_permission
@team = Team.find_by_id(params[:team_id]) @team = Team.find_by_id(params[:team_id])
render_403 if @team && !is_admin_of_team(@team) render_403 if @team && !is_admin_of_team(@team)
end
end end
end end
end end
end

View file

@ -1,27 +1,28 @@
import React from 'react'; import React from "react";
import PropTypes from 'prop-types'; import { func } from "prop-types";
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from "react-intl";
import { DropdownButton, MenuItem } from 'react-bootstrap'; import { DropdownButton, MenuItem } from "react-bootstrap";
const InviteUsersButton = props => const InviteUsersButton = props => (
<DropdownButton <DropdownButton
bsStyle={'primary'} bsStyle={"primary"}
title={<FormattedMessage id="invite_users.dropdown_button.invite" />} title={<FormattedMessage id="invite_users.dropdown_button.invite" />}
id="invite_users.submit_button" id="invite_users.submit_button"
> >
<MenuItem onClick={() => props.handleClick('guest')}> <MenuItem onClick={() => props.handleClick("guest")}>
<FormattedMessage id="invite_users.dropdown_button.guest" /> <FormattedMessage id="invite_users.dropdown_button.guest" />
</MenuItem> </MenuItem>
<MenuItem onClick={() => props.handleClick('normal_user')}> <MenuItem onClick={() => props.handleClick("normal_user")}>
<FormattedMessage id="invite_users.dropdown_button.normal_user" /> <FormattedMessage id="invite_users.dropdown_button.normal_user" />
</MenuItem> </MenuItem>
<MenuItem onClick={() => props.handleClick('admin')}> <MenuItem onClick={() => props.handleClick("admin")}>
<FormattedMessage id="invite_users.dropdown_button.admin" /> <FormattedMessage id="invite_users.dropdown_button.admin" />
</MenuItem> </MenuItem>
</DropdownButton>; </DropdownButton>
);
InviteUsersButton.propTypes = { InviteUsersButton.propTypes = {
handleClick: PropTypes.func.isRequired handleClick: func.isRequired
}; };
export default InviteUsersButton; export default InviteUsersButton;

View file

@ -1,38 +1,42 @@
import React from 'react'; import React from "react";
import PropTypes from 'prop-types'; import { string, func, arrayOf } from "prop-types";
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from "react-intl";
import { FormGroup, HelpBlock } from 'react-bootstrap'; import { FormGroup, HelpBlock } from "react-bootstrap";
import TagsInput from 'react-tagsinput'; import TagsInput from "react-tagsinput";
import { INVITE_USERS_LIMIT } from '../../../config/constants/numeric'; import { INVITE_USERS_LIMIT } from "../../../config/constants/numeric";
const InviteUsersForm = props => const InviteUsersForm = props => (
<FormGroup controlId="form-invite-user"> <FormGroup controlId="form-invite-user">
<p> <p>
<FormattedMessage id="invite_users.input_text" values={{team: props.teamName}} /> <FormattedMessage
</p> id="invite_users.input_text"
<TagsInput values={{ team: props.teamName }}
value={props.tags} />
addKeys={[9, 13, 188]} </p>
addOnPaste <TagsInput
onlyUnique value={props.tags}
maxTags={INVITE_USERS_LIMIT} addKeys={[9, 13, 188]}
inputProps={{ addOnPaste
placeholder: '' onlyUnique
}} maxTags={INVITE_USERS_LIMIT}
onChange={props.handleChange} inputProps={{
/> placeholder: ""
<HelpBlock> }}
<em> onChange={props.handleChange}
<FormattedMessage id="invite_users.input_help" /> />
</em> <HelpBlock>
</HelpBlock> <em>
</FormGroup>; <FormattedMessage id="invite_users.input_help" />
</em>
</HelpBlock>
</FormGroup>
);
InviteUsersForm.propTypes = { InviteUsersForm.propTypes = {
tags: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, tags: arrayOf(string.isRequired).isRequired,
handleChange: PropTypes.func.isRequired, handleChange: func.isRequired,
teamName: PropTypes.string.isRequired teamName: string.isRequired
}; };
export default InviteUsersForm; export default InviteUsersForm;

View file

@ -1,30 +1,38 @@
import React from 'react'; import React from "react";
import PropTypes from 'prop-types'; import { shape, arrayOf, string } from "prop-types";
import { Alert } from 'react-bootstrap'; import { Alert } from "react-bootstrap";
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from "react-intl";
const InviteUsersResults = props => const InviteUsersResults = props => (
<div> <div>
<h5> <h5>
<FormattedMessage id="invite_users.results_title" /> <FormattedMessage id="invite_users.results_title" />
</h5> </h5>
<hr /> <hr />
{props.results.invite_results.map(result => {props.results.invite_results.map(result => (
<Alert bsStyle={result.alert} key={result.email}> <Alert bsStyle={result.alert} key={result.email}>
<strong>{result.email}</strong> <strong>{result.email}</strong>
&nbsp;-&nbsp; &nbsp;-&nbsp;
<FormattedMessage id={`invite_users.results_msg.${result.status}`} <FormattedMessage
values={{team: props.results.team_name, id={`invite_users.results_msg.${result.status}`}
role: <FormattedMessage id={`invite_users.roles.${result.user_role}`} />, values={{
nr: result.invite_limit team: props.results.team_name,
}} /> role: (
</Alert> <FormattedMessage id={`invite_users.roles.${result.user_role}`} />
)} ),
</div>; nr: result.invite_limit
}}
/>
</Alert>
))}
</div>
);
InviteUsersResults.propTypes = { InviteUsersResults.propTypes = {
results: PropTypes.object.isRequired results: shape({
invite_results: arrayOf.isRequired,
team_name: string.isRequired
}).isRequired
}; };
export default InviteUsersResults; export default InviteUsersResults;

View file

@ -1,107 +1,115 @@
import React, { Component } from 'react'; import React, { Component } from "react";
import PropTypes from 'prop-types'; import { bool, func, shape, number, string } from "prop-types";
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from "react-intl";
import { Modal, ButtonToolbar, Button } from 'react-bootstrap'; import { Modal, ButtonToolbar, Button } from "react-bootstrap";
import styled from 'styled-components'; import styled from "styled-components";
import axios from '../../config/axios'; import axios from "../../config/axios";
import { INVITE_USERS_PATH, TEAM_DETAILS_PATH } from '../../config/api_endpoints'; import {
import InviteUsersForm from './components/InviteUsersForm'; INVITE_USERS_PATH,
import InviteUsersResults from './components/InviteUsersResults'; TEAM_DETAILS_PATH
import InviteUsersButton from './components/InviteUsersButton'; } from "../../config/api_endpoints";
import InviteUsersForm from "./components/InviteUsersForm";
import InviteUsersResults from "./components/InviteUsersResults";
import InviteUsersButton from "./components/InviteUsersButton";
const StyledButtonToolbar = styled(ButtonToolbar)` const StyledButtonToolbar = styled(ButtonToolbar)`float: right;`;
float: right;
`;
class InviteUsersModal extends Component { class InviteUsersModal extends Component {
constructor() { constructor(props) {
super(); super(props);
this.state = { this.state = {
showInviteUsersResults: false, showInviteUsersResults: false,
inputTags: [], inputTags: [],
inviteResults: [] inviteResults: []
}; };
this.handleInputChange = this.handleInputChange.bind(this); this.handleInputChange = this.handleInputChange.bind(this);
this.inviteAs = this.inviteAs.bind(this); this.inviteAs = this.inviteAs.bind(this);
this.handleCloseModal = this.handleCloseModal.bind(this) this.handleCloseModal = this.handleCloseModal.bind(this);
} }
handleCloseModal() { handleCloseModal() {
const path = TEAM_DETAILS_PATH.replace(":team_id", this.props.team.id); const path = TEAM_DETAILS_PATH.replace(":team_id", this.props.team.id);
this.props.onCloseModal(); this.props.onCloseModal();
this.setState({ this.setState({
showInviteUsersResults: false, showInviteUsersResults: false,
inputTags: [], inputTags: [],
inviteResults: [] inviteResults: []
}); });
// Update team members table // Update team members table
axios.get(path).then(response => { axios.get(path).then(response => {
const { users } = response.data.team_details; const { users } = response.data.team_details;
this.props.updateUsersCallback(users); this.props.updateUsersCallback(users);
}) });
} }
handleInputChange(inputTags) { handleInputChange(inputTags) {
this.setState({ inputTags }); this.setState({ inputTags });
} }
inviteAs(role) { inviteAs(role) {
axios axios
.put(INVITE_USERS_PATH, { .put(INVITE_USERS_PATH, {
user_role: role, user_role: role,
emails: this.state.inputTags, emails: this.state.inputTags,
team_id: this.props.team.id team_id: this.props.team.id
}) })
.then(({ data }) => { .then(({ data }) => {
this.setState({ inviteResults: data }); this.setState({ inviteResults: data });
this.setState({ showInviteUsersResults: true }); this.setState({ showInviteUsersResults: true });
}) })
.catch(error => {}); .catch(error => {});
} }
render() { render() {
let modalBody = null; let modalBody = null;
let inviteButton = null; let inviteButton = null;
if (this.state.showInviteUsersResults) { if (this.state.showInviteUsersResults) {
modalBody = <InviteUsersResults results={this.state.inviteResults} />; modalBody = <InviteUsersResults results={this.state.inviteResults} />;
inviteButton = null; inviteButton = null;
} else { } else {
modalBody = <InviteUsersForm tags={this.state.inputTags} handleChange={this.handleInputChange} teamName={this.props.team.name} />; modalBody = (
inviteButton = <InviteUsersButton handleClick={this.inviteAs} />; <InviteUsersForm
} tags={this.state.inputTags}
handleChange={this.handleInputChange}
teamName={this.props.team.name}
/>
);
inviteButton = <InviteUsersButton handleClick={this.inviteAs} />;
}
return ( return (
<Modal show={this.props.showModal} onHide={this.handleCloseModal}> <Modal show={this.props.showModal} onHide={this.handleCloseModal}>
<Modal.Header closeButton> <Modal.Header closeButton>
<Modal.Title> <Modal.Title>
<FormattedMessage id="invite_users.modal_title" values={{team: this.props.team.name}} /> <FormattedMessage
</Modal.Title> id="invite_users.modal_title"
</Modal.Header> values={{ team: this.props.team.name }}
<Modal.Body> />
{modalBody} </Modal.Title>
</Modal.Body> </Modal.Header>
<Modal.Footer> <Modal.Body>{modalBody}</Modal.Body>
<StyledButtonToolbar> <Modal.Footer>
<Button onClick={this.handleCloseModal}> <StyledButtonToolbar>
<FormattedMessage id="general.cancel" /> <Button onClick={this.handleCloseModal}>
</Button> <FormattedMessage id="general.cancel" />
{inviteButton} </Button>
</StyledButtonToolbar> {inviteButton}
</Modal.Footer> </StyledButtonToolbar>
</Modal> </Modal.Footer>
); </Modal>
} );
}
} }
InviteUsersModal.propTypes = { InviteUsersModal.propTypes = {
showModal: PropTypes.bool.isRequired, showModal: bool.isRequired,
onCloseModal: PropTypes.func.isRequired, onCloseModal: func.isRequired,
team: PropTypes.shape({ team: shape({
id: PropTypes.number.isRequired, id: number.isRequired,
name: PropTypes.string.isRequired name: string.isRequired
}).isRequired, }).isRequired,
updateUsersCallback: PropTypes.func.isRequired updateUsersCallback: func.isRequired
}; };
export default InviteUsersModal; export default InviteUsersModal;

View file

@ -39,7 +39,7 @@ export const CHANGE_USER_RECENT_NOTIFICATION_EMAIL_PATH =
"/client_api/users/change_recent_notification_email"; "/client_api/users/change_recent_notification_email";
export const CHANGE_USER_SYSTEM_MESSAGE_NOTIFICATION_EMAIL_PATH = export const CHANGE_USER_SYSTEM_MESSAGE_NOTIFICATION_EMAIL_PATH =
"/client_api/users/change_system_notification_email"; "/client_api/users/change_system_notification_email";
export const INVITE_USERS_PATH = '/client_api/users/invite_users'; export const INVITE_USERS_PATH = "/client_api/users/invite_users";
// info dropdown_title // info dropdown_title
export const CUSTOMER_SUPPORT_LINK = "http://scinote.net/support"; export const CUSTOMER_SUPPORT_LINK = "http://scinote.net/support";

View file

@ -1,25 +1,25 @@
export default { export default {
'en-US': { "en-US": {
general: { general: {
close: 'Close', close: "Close",
cancel: 'Cancel', cancel: "Cancel",
update: 'Update', update: "Update",
edit: 'Edit', edit: "Edit",
loading: 'Loading ...' loading: "Loading ..."
}, },
error_messages: { error_messages: {
text_too_short: "is too short (minimum is {min_length} characters)", 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: {
page_title: 'sciNote', page_title: "sciNote",
home_label: 'Home', home_label: "Home",
protocols_label: 'Protocols', protocols_label: "Protocols",
repositories_label: 'Repositories', repositories_label: "Repositories",
activities_label: 'Activities', activities_label: "Activities",
search_label: 'Search', search_label: "Search",
notifications_label: 'Notifications', notifications_label: "Notifications",
info_label: 'Info' info_label: "Info"
}, },
settings_page: { settings_page: {
all_teams: "All teams", all_teams: "All teams",
@ -54,8 +54,8 @@ export default {
"Assignment notifications appear whenever you get assigned to a team, project, task.", "Assignment notifications appear whenever you get assigned to a team, project, task.",
recent_changes: "Recent changes", recent_changes: "Recent changes",
recent_changes_msg: recent_changes_msg:
'Recent changes notifications appear whenever there is a change on a task you are assigned to.', "Recent changes notifications appear whenever there is a change on a task you are assigned to.",
system_message: 'System message', system_message: "System message",
system_message_msg: system_message_msg:
"System message notifications are specifically sent by site maintainers to notify all users about a system update.", "System message notifications are specifically sent by site maintainers to notify all users about a system update.",
show_in_scinote: "Show in sciNote", show_in_scinote: "Show in sciNote",
@ -64,20 +64,28 @@ export default {
yes: "Yes", yes: "Yes",
leave_team_modal: { leave_team_modal: {
title: "Leave team {teamName}", title: "Leave team {teamName}",
subtitle: "Are you sure you wish to leave team My projects? This action is irreversible.", subtitle:
"Are you sure you wish to leave team My projects? This action is irreversible.",
warnings: "Leaving team has following consequences:", warnings: "Leaving team has following consequences:",
warning_message_one: "you will lose access to all content belonging to the team (including projects, tasks, protocols and activities);", warning_message_one:
warning_message_two: "all projects in the team where you were the sole <b>Owner</b> will receive a new owner from the team administrators;", "you will lose access to all content belonging to the team (including projects, tasks, protocols and activities);",
warning_message_three: "all repository protocols in the team belonging to you will be reassigned onto a new owner from team administrators.", warning_message_two:
"all projects in the team where you were the sole <b>Owner</b> will receive a new owner from the team administrators;",
warning_message_three:
"all repository protocols in the team belonging to you will be reassigned onto a new owner from team administrators.",
leave_team: "Leave" leave_team: "Leave"
}, },
remove_user_modal: { remove_user_modal: {
title: "Remove user {user} from team {team}", title: "Remove user {user} from team {team}",
subtitle: "Are you sure you wish to remove user {user} from team {team}?", subtitle:
"Are you sure you wish to remove user {user} from team {team}?",
warnings: "Removing user from team has following consequences:", warnings: "Removing user from team has following consequences:",
warning_message_one: "user will lose access to all content belonging to the team (including projects, tasks, protocols and activities);", warning_message_one:
warning_message_two: "all projects in the team where user was the sole <b>Owner</b> will be reassigned onto you as a new owner;", "user will lose access to all content belonging to the team (including projects, tasks, protocols and activities);",
warning_message_three: "all repository protocols in the team belonging to user will be reassigned onto you.", warning_message_two:
"all projects in the team where user was the sole <b>Owner</b> will be reassigned onto you as a new owner;",
warning_message_three:
"all repository protocols in the team belonging to user will be reassigned onto you.",
remove_user: "Remove user" remove_user: "Remove user"
}, },
update_team_description_modal: { update_team_description_modal: {
@ -114,61 +122,65 @@ export default {
} }
}, },
activities: { activities: {
modal_title: 'Activities', modal_title: "Activities",
no_data: 'No Data', no_data: "No Data",
more_activities: 'More Activities' more_activities: "More Activities"
}, },
global_team_switch: { global_team_switch: {
new_team: 'New team' new_team: "New team"
}, },
notifications: { notifications: {
dropdown_title: 'Notifications', dropdown_title: "Notifications",
dropdown_settings_link: 'Settings', dropdown_settings_link: "Settings",
dropdown_show_all: 'Show all notifications' dropdown_show_all: "Show all notifications"
}, },
info_dropdown: { info_dropdown: {
customer_support: 'Customer support', customer_support: "Customer support",
tutorials: 'Tutorials', tutorials: "Tutorials",
release_notes: 'Release notes', release_notes: "Release notes",
premium: 'Premium', premium: "Premium",
contact_us: 'Contact us' contact_us: "Contact us"
}, },
user_account_dropdown: { user_account_dropdown: {
greeting: 'Hi, {name}', greeting: "Hi, {name}",
settings: 'Settings', settings: "Settings",
log_out: 'Log out' log_out: "Log out"
}, },
invite_users: { invite_users: {
modal_title: 'Invite users to team {team}', modal_title: "Invite users to team {team}",
input_text: 'Invite more people to team {team} and start using sciNote.', input_text: "Invite more people to team {team} and start using sciNote.",
input_help: 'Input one or multiple emails, confirm each email with ENTER key.', input_help:
"Input one or multiple emails, confirm each email with ENTER key.",
dropdown_button: { dropdown_button: {
invite: 'Invite user/s', invite: "Invite user/s",
guest: 'as Guest/s', guest: "as Guest/s",
normal_user: 'as Normal user/s', normal_user: "as Normal user/s",
admin: 'as Administrator/s' admin: "as Administrator/s"
}, },
results_title: 'Invitation results:', results_title: "Invitation results:",
roles: { roles: {
guest: 'Guest', guest: "Guest",
normal_user: 'Normal user', normal_user: "Normal user",
admin: 'Administrator' admin: "Administrator"
}, },
results_msg: { results_msg: {
user_exists: 'User is already a member of sciNote.', user_exists: "User is already a member of sciNote.",
user_exists_unconfirmed: user_exists_unconfirmed:
'User is already a member of sciNote but is not confirmed yet.', "User is already a member of sciNote but is not confirmed yet.",
user_exists_and_in_team_unconfirmed: user_exists_and_in_team_unconfirmed:
'User is already a member of sciNote and team {team} as {role} but is not confirmed yet.', "User is already a member of sciNote and team {team} as {role} but is not confirmed yet.",
user_exists_invited_to_team_unconfirmed: user_exists_invited_to_team_unconfirmed:
'User is already a member of sciNote but is not confirmed yet - successfully invited to team {team} as {role}.', "User is already a member of sciNote but is not confirmed yet - successfully invited to team {team} as {role}.",
user_exists_and_in_team: 'User is already a member of sciNote and team {team} as {role}.', user_exists_and_in_team:
"User is already a member of sciNote and team {team} as {role}.",
user_exists_invited_to_team: user_exists_invited_to_team:
'User was already a member of sciNote - successfully invited to team {team} as {role}.', "User was already a member of sciNote - successfully invited to team {team} as {role}.",
user_created: 'User succesfully invited to sciNote.', user_created: "User succesfully invited to sciNote.",
user_created_invited_to_team: 'User successfully invited to sciNote and team {team} as {role}.', user_created_invited_to_team:
user_invalid: 'Invalid email.', "User successfully invited to sciNote and team {team} as {role}.",
too_many_emails: 'Only invited first {nr} emails. To invite more users, fill in another invitation form.' user_invalid: "Invalid email.",
too_many_emails:
"Only invited first {nr} emails. To invite more users, fill in another invitation form."
} }
} }
} }

View file

@ -10,7 +10,7 @@ import {
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
import axios from "../../../../../config/axios"; import axios from "../../../../../config/axios";
import InviteUsersModal from '../../../../../components/InviteUsersModal'; import InviteUsersModal from "../../../../../components/InviteUsersModal";
import RemoveUserModal from "./RemoveUserModal"; import RemoveUserModal from "./RemoveUserModal";
import DataTable from "../../../../../components/data_table"; import DataTable from "../../../../../components/data_table";
import { UPDATE_USER_TEAM_ROLE_PATH } from "../../../../../config/api_endpoints"; import { UPDATE_USER_TEAM_ROLE_PATH } from "../../../../../config/api_endpoints";
@ -30,8 +30,12 @@ class TeamsMembers extends Component {
userToRemove: initalUserToRemove userToRemove: initalUserToRemove
}; };
this.memberAction = this.memberAction.bind(this); this.memberAction = this.memberAction.bind(this);
this.showInviteUsersModalCallback = this.showInviteUsersModalCallback.bind(this); this.showInviteUsersModalCallback = this.showInviteUsersModalCallback.bind(
this.closeInviteUsersModalCallback = this.closeInviteUsersModalCallback.bind(this); this
);
this.closeInviteUsersModalCallback = this.closeInviteUsersModalCallback.bind(
this
);
this.hideModal = this.hideModal.bind(this); this.hideModal = this.hideModal.bind(this);
} }
@ -184,7 +188,7 @@ class TeamsMembers extends Component {
<FormattedMessage id="settings_page.single_team.members_panel_title" /> <FormattedMessage id="settings_page.single_team.members_panel_title" />
} }
> >
<Button bsStyle='primary' onClick={this.showInviteUsersModalCallback}> <Button bsStyle="primary" onClick={this.showInviteUsersModalCallback}>
<Glyphicon glyph="plus" /> <Glyphicon glyph="plus" />
<FormattedMessage id="settings_page.single_team.add_members" /> <FormattedMessage id="settings_page.single_team.add_members" />
</Button> </Button>

View file

@ -1,26 +1,26 @@
@import 'constants'; @import "constants";
@import 'react-bootstrap-timezone-picker/dist/react-bootstrap-timezone-picker.min.css'; @import "react-bootstrap-timezone-picker/dist/react-bootstrap-timezone-picker.min.css";
@import '~react-bootstrap-table/dist/react-bootstrap-table.min'; @import "~react-bootstrap-table/dist/react-bootstrap-table.min";
@import 'react-tagsinput/react-tagsinput.css'; @import "react-tagsinput/react-tagsinput.css";
body { body {
background-color: $color-concrete; background-color: $color-concrete;
color: $color-emperor; color: $color-emperor;
font-family: "Open Sans", Arial, Helvetica, sans-serif; font-family: "Open Sans", Arial, Helvetica, sans-serif;
font-size: 13px; font-size: 13px;
} }
.label-primary { .label-primary {
background-color: $color-theme-primary; background-color: $color-theme-primary;
} }
.btn-primary { .btn-primary {
background-color: $color-theme-secondary; background-color: $color-theme-secondary;
border-color: $primary-hover-color; border-color: $primary-hover-color;
margin-right: 7px; margin-right: 7px;
&:hover { &:hover {
background-color: $primary-hover-color; background-color: $primary-hover-color;
} }
} }
// // fixes issue with dropdown in datatable // // fixes issue with dropdown in datatable

View file

@ -11,12 +11,14 @@ module ClientApi
raise ClientApi::CustomInvitationsError unless @emails && @team && @role raise ClientApi::CustomInvitationsError unless @emails && @team && @role
@emails && @emails.empty? { raise ClientApi::CustomInvitationsError } @emails && @emails.empty? { raise ClientApi::CustomInvitationsError }
@role && !UserTeam.roles.keys.include?(@role) { raise ClientApi::CustomInvitationsError } if @role && !UserTeam.roles.keys.include?(@role)
raise ClientApi::CustomInvitationsError
end
end end
def invitation def invitation
invite_results = [] invite_results = []
@emails.each_with_index do |email, index| @emails.each_with_index do |email, index|
result = {} result = {}
# Check invite users limit # Check invite users limit
@ -33,52 +35,13 @@ module ClientApi
# Check if user already exists # Check if user already exists
user = User.find_by_email(email) if User.exists?(email: email) user = User.find_by_email(email) if User.exists?(email: email)
# User does not exist result = if user.blank?
if user.blank? # User does not exist
password = generate_user_password handle_new_user(result, email, user)
# Validate the user data else
error = !(Constants::BASIC_EMAIL_REGEX === email) # User exists
error = validate_user(email, email, password).count > 0 unless error handle_existing_user(result, user)
end
if !error
user = invite_new_user(email)
result[:status] = :user_created
result[:alert] = :success
result[:user] = user
# Invitation to team
if @team.present?
user_team = create_user_team_relation_and_notification(user)
result[:status] = :user_created_invited_to_team
result[:user_team] = user_team
end
else
# Return invalid status
result[:status] = :user_invalid
result[:alert] = :danger
end
# User exists
else
result[:status] = :"#{:user_exists}#{:_unconfirmed if !user.confirmed?}"
result[:alert] = :info
result[:user] = user
# Invitation to team
if @team.present?
user_team =
UserTeam.where(user: user, team: @team).first if UserTeam.exists?(user: user, team: @team)
if user_team.present?
result[:status] = :"#{:user_exists_and_in_team}#{:_unconfirmed if !user.confirmed?}"
else
user_team = create_user_team_relation_and_notification(user)
result[:status] = :"#{:user_exists_invited_to_team}#{:_unconfirmed if !user.confirmed?}"
end
result[:user_team] = user_team
end
end
invite_results << result invite_results << result
end end
invite_results invite_results
@ -86,6 +49,61 @@ module ClientApi
private private
def handle_new_user(result, email, user)
password = generate_user_password
# Validate the user data
error = (Constants::BASIC_EMAIL_REGEX !~ email)
error = validate_user(email, email, password).count > 0 unless error
if !error
# Invite new user
user = invite_new_user(email)
result[:status] = :user_created
result[:alert] = :success
result[:user] = user
# Invitation to team
if @team.present?
user_team = create_user_team_relation_and_notification(user)
result[:status] = :user_created_invited_to_team
result[:user_team] = user_team
end
else
# Return invalid status
result[:status] = :user_invalid
result[:alert] = :danger
end
result
end
def handle_existing_user(result, user)
result[:status] =
:"#{:user_exists}#{:_unconfirmed unless user.confirmed?}"
result[:alert] = :info
result[:user] = user
# Invitation to team
if @team.present?
if UserTeam.exists?(user: user, team: @team)
user_team = UserTeam.where(user: user, team: @team).first
end
if user_team.present?
result[:status] =
:"#{:user_exists_and_in_team}#{:_unconfirmed unless user
.confirmed?}"
else
user_team = create_user_team_relation_and_notification(user)
result[:status] =
:"#{:user_exists_invited_to_team}#{:_unconfirmed unless user
.confirmed?}"
end
result[:user_team] = user_team
end
result
end
def invite_new_user(email) def invite_new_user(email)
user = User.invite!( user = User.invite!(
full_name: email, full_name: email,
@ -140,4 +158,4 @@ module ClientApi
end end
CustomInvitationsError = Class.new(StandardError) CustomInvitationsError = Class.new(StandardError)
end end

View file

@ -2,7 +2,11 @@ json.invite_results invite_results do |invite_result|
json.status invite_result[:status] json.status invite_result[:status]
json.alert invite_result[:alert] json.alert invite_result[:alert]
json.email invite_result[:email] json.email invite_result[:email]
json.user_role invite_result[:user_team].role if invite_result[:user_team].present? if invite_result[:user_team].present?
json.invite_limit invite_result[:invite_user_limit] if invite_result[:invite_user_limit].present? json.user_role invite_result[:user_team].role
end
if invite_result[:invite_user_limit].present?
json.invite_limit invite_result[:invite_user_limit]
end
end end
json.team_name team.name if team.present? json.team_name team.name if team.present?

View file

@ -83,7 +83,8 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div class="col-xs-24 col-sm-12"> <div class="col-xs-24 col-sm-12">
<a href="#" class="btn btn-primary pull-right row" data-trigger="invite-users" data-turbolinks="false" data-modal-id="team-invite-users-modal"> <a href="#" class="btn btn-primary pull-right row" data-trigger="invite-users"
data-turbolinks="false" data-modal-id="team-invite-users-modal">
<span class="glyphicon glyphicon-plus"></span> <span class="glyphicon glyphicon-plus"></span>
<%= t("users.settings.teams.edit.add_user") %> <%= t("users.settings.teams.edit.add_user") %>
</a> </a>