mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-02-02 13:12:13 +08:00
Merge pull request #269 from Ducz0r/lm-sci-569
Add a refactored invite users modal that can be reused throughout application
This commit is contained in:
commit
c88f834ba9
17 changed files with 633 additions and 577 deletions
189
app/assets/javascripts/sitewide/invite_users_modal.js.erb
Normal file
189
app/assets/javascripts/sitewide/invite_users_modal.js.erb
Normal file
|
@ -0,0 +1,189 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
function initializeModal(modal) {
|
||||
var modalDialog = modal.find('.modal-dialog');
|
||||
var type = modal.attr('data-type');
|
||||
var stepForm = modal.find('[data-role=step-form]');
|
||||
var stepResults = modal.find('[data-role=step-results]');
|
||||
var stepResultsDiv = modal.find('[data-role=step-results][data-clear]');
|
||||
var inviteBtn = modal.find('[data-role=invite-btn]');
|
||||
var inviteWithRoleDiv =
|
||||
modal.find('[data-role=invite-with-role-div]');
|
||||
var inviteWithRoleBtn =
|
||||
modal.find('[data-role=invite-with-role-btn]');
|
||||
var orgSelectorCheckbox =
|
||||
modal.find('[data-role=org-selector-checkbox]');
|
||||
var orgSelectorDropdown =
|
||||
modal.find('[data-role=org-selector-dropdown]');
|
||||
var orgSelectorDropdown2 = $();
|
||||
var tagsInput = modal.find('[data-role=tags-input]');
|
||||
|
||||
// Set max tags
|
||||
tagsInput.tagsinput({
|
||||
maxTags: <%= Constants::INVITE_USERS_LIMIT %>
|
||||
});
|
||||
|
||||
modal
|
||||
.on('show.bs.modal', function() {
|
||||
// This cannot be scoped outside this function
|
||||
// because it is generated via JS
|
||||
orgSelectorDropdown2 =
|
||||
orgSelectorDropdown
|
||||
.next('.btn-group.bootstrap-select.form-control')
|
||||
.find('button.dropdown-toggle, li');
|
||||
|
||||
// Show/hide correct step
|
||||
stepForm.show();
|
||||
stepResults.hide();
|
||||
|
||||
// Show/hide buttons & other elements
|
||||
switch (type) {
|
||||
case 'invite_to_org_with_role':
|
||||
case 'invite':
|
||||
case 'invite_with_org_selector':
|
||||
case 'invite_with_org_selector_and_role':
|
||||
inviteBtn.show();
|
||||
inviteWithRoleDiv.hide();
|
||||
break;
|
||||
case 'invite_to_org':
|
||||
inviteBtn.hide();
|
||||
inviteWithRoleDiv.show();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Checkbox toggle event
|
||||
if (
|
||||
type === 'invite_with_org_selector' ||
|
||||
type === 'invite_with_org_selector_and_role'
|
||||
) {
|
||||
orgSelectorCheckbox.on('change', function() {
|
||||
if ($(this).is(':checked')) {
|
||||
orgSelectorDropdown.removeAttr('disabled');
|
||||
orgSelectorDropdown2.removeClass('disabled');
|
||||
if (type === 'invite_with_org_selector') {
|
||||
inviteBtn.hide();
|
||||
inviteWithRoleDiv.show();
|
||||
}
|
||||
} else {
|
||||
orgSelectorDropdown.attr('disabled', 'disabled');
|
||||
orgSelectorDropdown2.addClass('disabled');
|
||||
if (type === 'invite_with_org_selector') {
|
||||
inviteBtn.show();
|
||||
inviteWithRoleDiv.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle depending on input tags
|
||||
tagsInput
|
||||
.on('itemAdded', function(event) {
|
||||
inviteBtn.removeAttr('disabled');
|
||||
inviteWithRoleBtn.removeAttr('disabled');
|
||||
})
|
||||
.on('itemRemoved', function(event) {
|
||||
if ($(this).val() === null) {
|
||||
inviteBtn.attr('disabled', 'disabled');
|
||||
inviteWithRoleBtn.attr('disabled', 'disabled');
|
||||
}
|
||||
});
|
||||
|
||||
// Click action
|
||||
modal.find('[data-action=invite]').on('click', function() {
|
||||
animateSpinner(modalDialog);
|
||||
|
||||
var data = {
|
||||
emails: tagsInput.val()
|
||||
};
|
||||
switch (type) {
|
||||
case 'invite_to_org':
|
||||
data.organizationId = modal.attr('data-organization-id');
|
||||
data.role = $(this).attr('data-organization-role');
|
||||
break;
|
||||
case 'invite_to_org_with_role':
|
||||
data.organizationId = modal.attr('data-organization-id');
|
||||
data.role = modal.attr('data-organization-role');
|
||||
break;
|
||||
case 'invite':
|
||||
break;
|
||||
case 'invite_with_org_selector':
|
||||
if (orgSelectorCheckbox.is(':checked')) {
|
||||
data.organizationId = orgSelectorDropdown.val();
|
||||
data.role = $(this).attr('data-organization-role');
|
||||
}
|
||||
break;
|
||||
case 'invite_with_org_selector_and_role':
|
||||
if (orgSelectorCheckbox.is(':checked')) {
|
||||
data.organizationId = orgSelectorDropdown.val();
|
||||
data.role = modal.attr('data-organization-role');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: modal.attr('data-url'),
|
||||
dataType: 'json',
|
||||
data: data,
|
||||
success: function(data) {
|
||||
animateSpinner(modalDialog, false);
|
||||
stepForm.hide();
|
||||
stepResultsDiv.html(data.html);
|
||||
stepResults.show();
|
||||
// Add 'data-invited="true"' status to modal element
|
||||
modal.attr('data-invited', 'true');
|
||||
},
|
||||
error: function() {
|
||||
animateSpinner(modalDialog, false);
|
||||
modal.modal('hide');
|
||||
alert('Error inviting users.');
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.on('shown.bs.modal', function() {
|
||||
tagsInput.tagsinput('focus');
|
||||
|
||||
// Remove 'data-invited="true"' status
|
||||
modal.removeAttr('data-invited');
|
||||
})
|
||||
.on('hide.bs.modal', function() {
|
||||
// 'Reset' modal state
|
||||
tagsInput.tagsinput('removeAll');
|
||||
orgSelectorCheckbox.prop('checked', false);
|
||||
inviteBtn.attr('disabled', 'disabled');
|
||||
inviteWithRoleBtn.attr('disabled', 'disabled');
|
||||
orgSelectorDropdown2.addClass('disabled');
|
||||
animateSpinner(modalDialog, false);
|
||||
|
||||
// Unbind event listeners
|
||||
orgSelectorCheckbox.off('change');
|
||||
tagsInput.off('itemAdded itemRemoved');
|
||||
|
||||
// Hide contents of the results <div>
|
||||
stepResultsDiv.html('');
|
||||
stepResults.hide();
|
||||
stepForm.show();
|
||||
});
|
||||
}
|
||||
|
||||
function initializeModalsToggle() {
|
||||
$("[data-trigger='invite-users']").on('click', function() {
|
||||
var id = $(this).attr('data-modal-id');
|
||||
$('[data-role=invite-users-modal][data-id=' + id + ']')
|
||||
.modal('show');
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('[data-role=invite-users-modal]').each(function() {
|
||||
initializeModal($(this));
|
||||
});
|
||||
initializeModalsToggle();
|
||||
});
|
||||
})();
|
|
@ -1,5 +1,4 @@
|
|||
//= require datatables
|
||||
//= require users/settings/organizations/add_user_modal
|
||||
|
||||
var usersDatatable = null;
|
||||
|
||||
|
@ -236,8 +235,19 @@ function initRemoveUsers() {
|
|||
);
|
||||
}
|
||||
|
||||
function initReloadPageAfterInviteUsers() {
|
||||
$('[data-id=org-invite-users-modal]')
|
||||
.on('hidden.bs.modal', function() {
|
||||
if (!_.isUndefined($(this).attr('data-invited'))) {
|
||||
// Reload the whole table
|
||||
usersDatatable.ajax.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initEditName();
|
||||
initEditDescription();
|
||||
initUsersTable();
|
||||
initUpdateRoles();
|
||||
initRemoveUsers();
|
||||
initRemoveUsers();
|
||||
initReloadPageAfterInviteUsers();
|
||||
|
|
|
@ -1,171 +0,0 @@
|
|||
/* Global selectors */
|
||||
var modal = $("#add-user-modal");
|
||||
var modalContent = modal.find(".modal-content");
|
||||
var invitingExisting = true;
|
||||
var inviteButton = $("[data-id='invite-btn']");
|
||||
var inviteLinks = $("[data-action='invite']");
|
||||
var inviteExistingCollapsible = $("#invite-existing");
|
||||
var inviteExistingForm = $("[data-id='invite-existing-form']");
|
||||
var inviteExistingQuery = $("#existing_query");
|
||||
var inviteExistingResults = $("#invite-existing-results");
|
||||
var inviteNewCollapsible = $("#invite-new");
|
||||
var inviteNewForm = $("[data-id='invite-new-form']");
|
||||
var inviteNewRoleInput = $("[data-id='new-user-role-input']");
|
||||
var inviteNewNameInput = $("[data-id='invite-new-name-input']");
|
||||
var inviteNewEmailInput = $("[data-id='invite-new-email-input']");
|
||||
|
||||
function disableInviteBtn() {
|
||||
inviteButton.attr("disabled", "disabled");
|
||||
}
|
||||
function enableInviteBtn() {
|
||||
inviteButton.removeAttr("disabled");
|
||||
}
|
||||
|
||||
/**
|
||||
* General modal configuration & toggling.
|
||||
*/
|
||||
modal
|
||||
.on("shown.bs.modal", function() {
|
||||
// Focus the invite existing input
|
||||
inviteExistingQuery.focus();
|
||||
invitingExisting = true;
|
||||
})
|
||||
.on("hidden.bs.modal", function() {
|
||||
// Disable invite button,
|
||||
// reset forms, reset rendered content
|
||||
disableInviteBtn();
|
||||
inviteExistingForm.clearFormFields();
|
||||
inviteExistingForm.clearFormErrors();
|
||||
inviteExistingResults.html("");
|
||||
inviteNewForm.clearFormFields();
|
||||
inviteNewForm.clearFormErrors();
|
||||
});
|
||||
|
||||
inviteExistingCollapsible
|
||||
.on("hidden.bs.collapse", function() {
|
||||
// Reset form & rendered content
|
||||
inviteExistingForm.clearFormFields();
|
||||
inviteExistingForm.clearFormErrors();
|
||||
inviteExistingResults.html("");
|
||||
})
|
||||
.on("hide.bs.collapse", function() {
|
||||
// Disable invite button
|
||||
disableInviteBtn();
|
||||
})
|
||||
.on("shown.bs.collapse", function() {
|
||||
// Focus input when collapsible is shown
|
||||
inviteExistingQuery.focus();
|
||||
invitingExisting = true;
|
||||
});
|
||||
|
||||
inviteNewCollapsible
|
||||
.on("hidden.bs.collapse", function() {
|
||||
// Reset form
|
||||
inviteNewForm.clearFormFields();
|
||||
inviteNewForm.clearFormErrors();
|
||||
})
|
||||
.on("hide.bs.collapse", function() {
|
||||
// Disable invite button
|
||||
disableInviteBtn();
|
||||
})
|
||||
.on("shown.bs.collapse", function() {
|
||||
// Focus input when collapsible is shown
|
||||
inviteNewNameInput.focus();
|
||||
invitingExisting = false;
|
||||
});
|
||||
|
||||
// Invite links simply submit either of the forms
|
||||
inviteLinks.on("click", function() {
|
||||
var $this = $(this);
|
||||
|
||||
if (invitingExisting) {
|
||||
var form =
|
||||
inviteExistingResults
|
||||
.find("form[data-id='create-user-organization-form']");
|
||||
|
||||
// Set the role value in the form
|
||||
form
|
||||
.find("[data-id='existing-user-role-input']")
|
||||
.attr("value", $this.attr("data-value"));
|
||||
|
||||
// Submit the form inside "invite existing"
|
||||
animateSpinner(modalContent);
|
||||
form.submit();
|
||||
} else {
|
||||
// Set the role value in the form
|
||||
inviteNewRoleInput
|
||||
.attr("value", $this.attr("data-value"));
|
||||
|
||||
// Submit the form inside "invite new"
|
||||
animateSpinner(modalContent);
|
||||
inviteNewForm.submit();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Invite existing user functionality.
|
||||
*/
|
||||
|
||||
// Invite existing form submission
|
||||
modal
|
||||
.on("ajax:success", inviteExistingForm.selector, function(ev, data, status) {
|
||||
// Clear form errors
|
||||
inviteExistingForm.clearFormErrors();
|
||||
|
||||
// Alright, render the html
|
||||
inviteExistingResults.html(data.html);
|
||||
|
||||
// Disable invite button
|
||||
disableInviteBtn();
|
||||
})
|
||||
.on("ajax:error", inviteExistingForm.selector, function(ev, data, status) {
|
||||
//Clear previous info
|
||||
inviteExistingResults.html("");
|
||||
|
||||
// Display form errors
|
||||
inviteExistingForm.renderFormErrors("", data.responseJSON);
|
||||
});
|
||||
|
||||
// Update values & enable "invite" button
|
||||
// when user clicks on existing user
|
||||
inviteExistingResults
|
||||
.on("change", "[data-action='select-existing-user']", function() {
|
||||
var $this = $(this);
|
||||
// Set the hidden input user ID
|
||||
$("[data-id='existing-user-id-input']")
|
||||
.attr("value", $this.attr("data-user-id"));
|
||||
|
||||
// Enable button
|
||||
enableInviteBtn();
|
||||
});
|
||||
|
||||
/**
|
||||
* Invite new user functionality.
|
||||
*/
|
||||
|
||||
inviteNewForm
|
||||
.on("ajax:success", function(ev, data, status) {
|
||||
// Reload the page
|
||||
location.reload();
|
||||
})
|
||||
.on("ajax:error", function(ev, data, status) {
|
||||
// Render form errors
|
||||
animateSpinner(modalContent, false);
|
||||
$(this).renderFormErrors("user", data.responseJSON);
|
||||
});
|
||||
|
||||
|
||||
// Enable/disable invite button depending whether
|
||||
// any of the new user inputs are empty
|
||||
inviteNewForm
|
||||
.on("input", "input[data-role='input']", function() {
|
||||
if (
|
||||
_.isEmpty(inviteNewNameInput.val()) ||
|
||||
_.isEmpty(inviteNewEmailInput.val())
|
||||
) {
|
||||
disableInviteBtn();
|
||||
} else {
|
||||
enableInviteBtn();
|
||||
}
|
||||
});
|
||||
|
|
@ -1496,3 +1496,30 @@ html.turbolinks-progress-bar::before {
|
|||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-invite-users {
|
||||
.bootstrap-tagsinput {
|
||||
min-width: 450px;
|
||||
}
|
||||
|
||||
.results-container .alert {
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.results-container .results-wrap {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.org-selector .heading {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
input[type=checkbox] {
|
||||
vertical-align: middle;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +1,164 @@
|
|||
class Users::InvitationsController < Devise::InvitationsController
|
||||
module Users
|
||||
class InvitationsController < Devise::InvitationsController
|
||||
include UsersGenerator
|
||||
|
||||
def update
|
||||
@org = Organization.new
|
||||
@org.name = params[:organization][:name]
|
||||
before_action :check_invite_users_permission, only: :invite_users
|
||||
|
||||
super do |user|
|
||||
if user.errors.empty?
|
||||
@org.created_by = user
|
||||
@org.save
|
||||
def update
|
||||
@org = Organization.new
|
||||
@org.name = params[:organization][:name]
|
||||
|
||||
UserOrganization.create(
|
||||
user: user,
|
||||
organization: @org,
|
||||
role: 'admin'
|
||||
)
|
||||
super do |user|
|
||||
if user.errors.empty?
|
||||
@org.created_by = user
|
||||
@org.save
|
||||
|
||||
UserOrganization.create(
|
||||
user: user,
|
||||
organization: @org,
|
||||
role: 'admin'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def accept_resource
|
||||
resource = super
|
||||
|
||||
if not @org.valid?
|
||||
resource.errors.add(:base, @org.errors.to_a.first)
|
||||
def accept_resource
|
||||
resource = super
|
||||
resource.errors.add(:base, @org.errors.to_a.first) unless @org.valid?
|
||||
resource
|
||||
end
|
||||
|
||||
resource
|
||||
def invite_users
|
||||
@invite_results = []
|
||||
@too_many_emails = false
|
||||
|
||||
cntr = 0
|
||||
@emails.each do |email|
|
||||
cntr += 1
|
||||
|
||||
if cntr > Constants::INVITE_USERS_LIMIT
|
||||
@too_many_emails = true
|
||||
break
|
||||
end
|
||||
|
||||
password = generate_user_password
|
||||
|
||||
# Check if user already exists
|
||||
user = nil
|
||||
user = User.find_by_email(email) if User.exists?(email: email)
|
||||
|
||||
result = { email: email }
|
||||
|
||||
if user.present?
|
||||
result[:status] = :user_exists
|
||||
result[:user] = user
|
||||
else
|
||||
# Validate the user data
|
||||
error = !(Constants::BASIC_EMAIL_REGEX === email)
|
||||
error = validate_user(email, email, password).count > 0 unless error
|
||||
|
||||
if !error
|
||||
user = User.invite!(
|
||||
full_name: email,
|
||||
email: email,
|
||||
initials: email.upcase[0..1],
|
||||
skip_invitation: true
|
||||
)
|
||||
|
||||
result[:status] = :user_created
|
||||
result[:user] = user
|
||||
|
||||
# Sending email invitation is done in background job to prevent
|
||||
# issues with email delivery. Also invite method must be call
|
||||
# with :skip_invitation attribute set to true - see above.
|
||||
user.delay.deliver_invitation
|
||||
else
|
||||
# Return invalid status
|
||||
result[:status] = :user_invalid
|
||||
end
|
||||
end
|
||||
|
||||
if @org.present? && result[:status] != :user_invalid
|
||||
if UserOrganization.exists?(user: user, organization: @org)
|
||||
user_org =
|
||||
UserOrganization.where(user: user, organization: @org).first
|
||||
|
||||
result[:status] = :user_exists_and_in_org
|
||||
elsif result[:status] == :user_exists && !user.confirmed?
|
||||
# We don't want to allow inviting unconfirmed
|
||||
# users (that were not invited as part of this action)
|
||||
# into organizations
|
||||
result[:status] = :user_exists_unconfirmed
|
||||
else
|
||||
# Also generate user organization relation
|
||||
user_org = UserOrganization.new(
|
||||
user: user,
|
||||
organization: @org,
|
||||
role: @role
|
||||
)
|
||||
user_org.save
|
||||
|
||||
generate_notification(
|
||||
@user,
|
||||
user,
|
||||
user_org.role_str,
|
||||
user_org.organization
|
||||
)
|
||||
|
||||
if result[:status] == :user_exists
|
||||
result[:status] = :user_exists_invited_to_org
|
||||
else
|
||||
result[:status] = :user_created_invited_to_org
|
||||
end
|
||||
end
|
||||
|
||||
result[:user_org] = user_org
|
||||
end
|
||||
|
||||
@invite_results << result
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'shared/invite_users_modal_results.html.erb'
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_notification(user, target_user, role, org)
|
||||
title = I18n.t('notifications.assign_user_to_organization',
|
||||
assigned_user: target_user.name,
|
||||
role: role,
|
||||
organization: org.name,
|
||||
assigned_by_user: user.name)
|
||||
|
||||
message = "#{I18n.t('search.index.organization')} #{org.name}"
|
||||
notification = Notification.create(
|
||||
type_of: :assignment,
|
||||
title: ActionController::Base.helpers.sanitize(title),
|
||||
message: ActionController::Base.helpers.sanitize(message)
|
||||
)
|
||||
|
||||
if target_user.assignments_notification
|
||||
UserNotification.create(notification: notification, user: target_user)
|
||||
end
|
||||
end
|
||||
|
||||
def check_invite_users_permission
|
||||
@user = current_user
|
||||
@emails = params[:emails]
|
||||
@org = Organization.find_by_id(params['organizationId'])
|
||||
@role = params['role']
|
||||
|
||||
render_403 if @emails && @emails.empty?
|
||||
render_403 if @org && !is_admin_of_organization(@org)
|
||||
render_403 if @role && !UserOrganization.roles.keys.include?(@role)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,13 +21,7 @@ class Users::SettingsController < ApplicationController
|
|||
:destroy_organization,
|
||||
:organization_name,
|
||||
:organization_description,
|
||||
:search_organization_users,
|
||||
:organization_users_datatable,
|
||||
:create_user_and_user_organization
|
||||
]
|
||||
|
||||
before_action :check_create_user_organization_permission, only: [
|
||||
:create_user_organization
|
||||
:organization_users_datatable
|
||||
]
|
||||
|
||||
before_action :check_user_organization_permission, only: [
|
||||
|
@ -118,57 +112,6 @@ class Users::SettingsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def search_organization_users
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
if params.include?(:existing_query) && params[:existing_query].strip
|
||||
query = params[:existing_query].strip
|
||||
if query.length < Constants::NAME_MIN_LENGTH
|
||||
render json: {
|
||||
"existing_query": [
|
||||
t('general.query.length_too_short',
|
||||
min_length: Constants::NAME_MIN_LENGTH)
|
||||
]
|
||||
},
|
||||
status: :unprocessable_entity
|
||||
elsif query.length > Constants::NAME_MAX_LENGTH
|
||||
render json: {
|
||||
"existing_query": [
|
||||
t('general.query.length_too_long',
|
||||
max_length: Constants::NAME_MAX_LENGTH)
|
||||
]
|
||||
},
|
||||
status: :unprocessable_entity
|
||||
else
|
||||
# Okay, query exists and is non-blank, find users
|
||||
nr_of_results = User.search(true, query, @org).count
|
||||
|
||||
|
||||
users = User.search(false, query, @org)
|
||||
.limit(Constants::MODAL_SEARCH_LIMIT)
|
||||
|
||||
nr_of_members = User.organization_search(false, query, @org).count
|
||||
|
||||
render json: {
|
||||
html: render_to_string({
|
||||
partial: "users/settings/organizations/existing_users_search_results.html.erb",
|
||||
locals: {
|
||||
users: users,
|
||||
nr_of_results: nr_of_results,
|
||||
nr_of_members: nr_of_members,
|
||||
org: @org,
|
||||
query: query
|
||||
}
|
||||
})
|
||||
}
|
||||
end
|
||||
else
|
||||
render json: {}, status: :bad_request
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def organization_users_datatable
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
|
@ -213,101 +156,6 @@ class Users::SettingsController < ApplicationController
|
|||
redirect_to action: :organizations
|
||||
end
|
||||
|
||||
def create_user_organization
|
||||
@new_user_org = UserOrganization.new(create_user_organization_params)
|
||||
|
||||
# Check if such association doesn't exist already
|
||||
if !UserOrganization.where(
|
||||
user: @new_user_org.user,
|
||||
organization: @new_user_org.organization
|
||||
).exists? && @new_user_org.save
|
||||
|
||||
generate_notification(@user_organization.user,
|
||||
@new_user_org.user,
|
||||
@new_user_org.role_str,
|
||||
@new_user_org.organization)
|
||||
|
||||
flash[:notice] = I18n.t(
|
||||
'users.settings.organizations.edit.modal_add_user.existing_flash_success',
|
||||
user: @new_user_org.user.full_name,
|
||||
role: @new_user_org.role_str
|
||||
)
|
||||
else
|
||||
flash[:alert] =
|
||||
I18n.t('users.settings.organizations.edit.modal_add_user.existing_flash_error')
|
||||
end
|
||||
|
||||
# Either way, redirect back to organization page
|
||||
redirect_to action: :organization,
|
||||
organization_id: @new_user_org.organization_id
|
||||
end
|
||||
|
||||
def create_user_and_user_organization
|
||||
respond_to do |format|
|
||||
# User & organization
|
||||
# parameters are already taken care of,
|
||||
# so only role needs to be verified
|
||||
if !params.include? :role or
|
||||
!UserOrganization.roles.keys.include? params[:role]
|
||||
format.json {
|
||||
render json: "Invalid role provided",
|
||||
status: :unprocessable_entity
|
||||
}
|
||||
else
|
||||
password = generate_user_password
|
||||
user_params = create_user_params
|
||||
full_name = user_params[:full_name]
|
||||
email = user_params[:email]
|
||||
|
||||
# Validate the user data
|
||||
errors = validate_user(full_name, email, password)
|
||||
|
||||
if errors.count == 0
|
||||
@user = User.invite!(
|
||||
full_name: full_name,
|
||||
email: email,
|
||||
initials: full_name.split(" ").map{|w| w[0].upcase}.join[0..3],
|
||||
skip_invitation: true
|
||||
)
|
||||
|
||||
# Sending email invitation is done in background job to prevent
|
||||
# issues with email delivery. Also invite method must be call
|
||||
# with :skip_invitation attribute set to true - see above.
|
||||
@user.delay.deliver_invitation
|
||||
|
||||
# Also generate user organization relation
|
||||
@user_org = UserOrganization.new(
|
||||
user: @user,
|
||||
organization: @org,
|
||||
role: params[:role]
|
||||
)
|
||||
@user_org.save
|
||||
|
||||
# Flash message
|
||||
flash[:notice] = t(
|
||||
"users.settings.organizations.edit.modal_add_user.new_flash_success",
|
||||
user: @user.full_name,
|
||||
role: @user_org.role_str,
|
||||
email: @user.email
|
||||
)
|
||||
flash.keep
|
||||
|
||||
# Return success!
|
||||
format.json {
|
||||
render json: {
|
||||
status: :ok
|
||||
}
|
||||
}
|
||||
else
|
||||
format.json {
|
||||
render json: errors,
|
||||
status: :unprocessable_entity
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_user_organization
|
||||
respond_to do |format|
|
||||
if @user_org.update(update_user_organization_params)
|
||||
|
@ -520,13 +368,6 @@ class Users::SettingsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def check_create_user_organization_permission
|
||||
@org = Organization.find_by_id(params[:user_organization][:organization_id])
|
||||
unless is_admin_of_organization(@org)
|
||||
render_403
|
||||
end
|
||||
end
|
||||
|
||||
def check_user_organization_permission
|
||||
@user_org = UserOrganization.find_by_id(params[:user_organization_id])
|
||||
@org = @user_org.organization
|
||||
|
@ -565,41 +406,12 @@ class Users::SettingsController < ApplicationController
|
|||
)
|
||||
end
|
||||
|
||||
def create_user_organization_params
|
||||
params.require(:user_organization).permit(
|
||||
:user_id,
|
||||
:organization_id,
|
||||
:role
|
||||
)
|
||||
end
|
||||
|
||||
def update_user_organization_params
|
||||
params.require(:user_organization).permit(
|
||||
:role
|
||||
)
|
||||
end
|
||||
|
||||
def generate_notification(user, target_user, role, org)
|
||||
title = I18n.t('notifications.assign_user_to_organization',
|
||||
assigned_user: target_user.name,
|
||||
role: role,
|
||||
organization: org.name,
|
||||
assigned_by_user: user.name)
|
||||
|
||||
message = "#{I18n.t('search.index.organization')} #{org.name}"
|
||||
notification = Notification.create(
|
||||
type_of: :assignment,
|
||||
title:
|
||||
ActionController::Base.helpers.sanitize(title),
|
||||
message:
|
||||
ActionController::Base.helpers.sanitize(message),
|
||||
)
|
||||
|
||||
if target_user.assignments_notification
|
||||
UserNotification.create(notification: notification, user: target_user)
|
||||
end
|
||||
end
|
||||
|
||||
def reset_user_current_organization(user_org)
|
||||
ids = user_org.user.organizations_ids
|
||||
ids -= [user_org.organization.id]
|
||||
|
|
|
@ -133,39 +133,6 @@ class User < ActiveRecord::Base
|
|||
.distinct
|
||||
end
|
||||
|
||||
# Search all active users inside given organization for
|
||||
# username & email.
|
||||
def self.organization_search(
|
||||
active_only,
|
||||
query = nil,
|
||||
organization = nil
|
||||
)
|
||||
|
||||
if !organization.present?
|
||||
result = nil
|
||||
else
|
||||
|
||||
result = User.all
|
||||
|
||||
if active_only
|
||||
result = result.where.not(confirmed_at: nil)
|
||||
end
|
||||
|
||||
ignored_ids =
|
||||
UserOrganization
|
||||
.select(:user_id)
|
||||
.where(organization_id: organization.id)
|
||||
result =
|
||||
result
|
||||
.where("users.id IN (?)", ignored_ids)
|
||||
|
||||
result
|
||||
.where_attributes_like([:full_name, :email], query)
|
||||
.distinct
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def empty_avatar(name, size)
|
||||
file_ext = name.split(".").last
|
||||
self.avatar_file_name = name
|
||||
|
|
145
app/views/shared/_invite_users_modal.html.erb
Normal file
145
app/views/shared/_invite_users_modal.html.erb
Normal file
|
@ -0,0 +1,145 @@
|
|||
<%
|
||||
# How to use this modal:
|
||||
# 1. Render it in the page (HTML) of your choice
|
||||
# 2. Add an element (e.g. <a href>) with following attributes:
|
||||
# * data-trigger="invite-users",
|
||||
# * data-modal-id="modal-id",
|
||||
#
|
||||
# Modal parameters:
|
||||
# * modal_id: unique id so the JS works if multiple modals are present
|
||||
# on the same page
|
||||
# * type:
|
||||
# * 'invite_to_org' => params: organization
|
||||
# * 'invite_to_org_with_role' => params: organization, role
|
||||
# * 'invite',
|
||||
# * 'invite_with_org_selector',
|
||||
# * 'invite_with_org_selector_and_role' => params: role
|
||||
# * organization: invite users to the specified organization
|
||||
# * role: all users are invited as the specified role
|
||||
# * (optional) text_title: custom title text for modal
|
||||
# * (optional) text_invite_heading: custom invite heading text for modal
|
||||
%>
|
||||
<%
|
||||
text_title ||= nil
|
||||
text_invite_heading ||= nil
|
||||
invite_to_org = type.in?(%w(invite_to_org invite_to_org_with_role))
|
||||
%>
|
||||
<div
|
||||
class="modal modal-invite-users"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
aria-labelledby="invite-users-modal-label"
|
||||
data-id="<%= modal_id %>"
|
||||
data-role="invite-users-modal"
|
||||
data-type="<%= type %>"
|
||||
data-url="<%= invite_users_path %>"
|
||||
data-backdrop="static"
|
||||
data-keyboard="false"
|
||||
<%= "data-organization-id=#{organization.id}" if invite_to_org %>
|
||||
<%= "data-organization-role=#{role}" if type.in?(%w(invite_to_org_with_role invite_with_org_selector_and_role)) %>
|
||||
>
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">
|
||||
<% if text_title %>
|
||||
<%= text_title %>
|
||||
<% else %>
|
||||
<% if invite_to_org %>
|
||||
<%= t('invite_users.to_org.title', organization: organization.name) %>
|
||||
<% else %>
|
||||
<%= t('invite_users.no_org.title') %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div data-role="step-form">
|
||||
<p>
|
||||
<% if text_invite_heading %>
|
||||
<%= text_invite_heading %>
|
||||
<% else %>
|
||||
<% if invite_to_org %>
|
||||
<%= t('invite_users.to_org.heading', organization: organization.name) %>
|
||||
<% else %>
|
||||
<%= t('invite_users.no_org.heading') %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
<select class="emails-input" multiple data-role="tags-input" name="emails[]">
|
||||
</select>
|
||||
<br />
|
||||
<em><%= t('invite_users.input_subtitle') %></em>
|
||||
|
||||
<% if type.in?(['invite_with_org_selector', 'invite_with_org_selector_and_role']) %>
|
||||
<% # Only allow inviting to organizations where user is admin %>
|
||||
<% uos = current_user ? current_user.user_organizations.where(role: UserOrganization.roles[:admin]).joins(:organization) : [] %>
|
||||
<% if uos.count > 0 %>
|
||||
<div class="org-selector">
|
||||
<div class="heading">
|
||||
<input type="checkbox" data-role="org-selector-checkbox" />
|
||||
<span><%= t('invite_users.invite_to_org_heading') %></span>
|
||||
</div>
|
||||
<%= select_tag(
|
||||
'organization-select',
|
||||
options_for_select(
|
||||
uos.pluck('organizations.name', 'organizations.id')
|
||||
),
|
||||
{
|
||||
class: 'form-control selectpicker',
|
||||
'data-role' => 'org-selector-dropdown',
|
||||
disabled: 'disabled'
|
||||
}
|
||||
) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="results-container" data-role="step-results" data-clear="true">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<div data-role="step-form">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
<%= t('general.cancel') %>
|
||||
</button>
|
||||
|
||||
<!-- Invite buttons -->
|
||||
<button type="button" data-role="invite-btn" class="btn btn-primary" disabled="disabled" data-action="invite">
|
||||
<%= t('invite_users.invite_btn') %>
|
||||
</button>
|
||||
|
||||
<div class="btn-group" data-role="invite-with-role-div">
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" data-id="invite-btn" aria-haspopup="true" aria-expanded="false" data-role="invite-with-role-btn" disabled="disabled">
|
||||
<%= t('invite_users.invite_btn') %>
|
||||
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<%= link_to t('invite_users.invite_guest'), '#', data: { action: 'invite', 'organization-role' => 'guest' } %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to t('invite_users.invite_user'), '#', data: { action: 'invite', 'organization-role' => 'normal_user' } %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to t('invite_users.invite_admin'), '#', data: { action: 'invite', 'organization-role' => 'admin' } %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="step-results">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
<%= t('general.close') %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
54
app/views/shared/_invite_users_modal_results.html.erb
Normal file
54
app/views/shared/_invite_users_modal_results.html.erb
Normal file
|
@ -0,0 +1,54 @@
|
|||
<h5>
|
||||
<%= t('invite_users.results.heading') %>
|
||||
</h5>
|
||||
<hr />
|
||||
<div class="results-wrap">
|
||||
<% @invite_results.each do |result| %>
|
||||
<% if result[:status] == :user_exists %>
|
||||
<div class="alert alert-info" role="alert">
|
||||
<strong><%= result[:email] %></strong>
|
||||
-
|
||||
<%= t('invite_users.results.user_exists') %>
|
||||
</div>
|
||||
<% elsif result[:status] == :user_exists_unconfirmed %>
|
||||
<div class="alert alert-info" role="alert">
|
||||
<strong><%= result[:email] %></strong>
|
||||
-
|
||||
<%= t('invite_users.results.user_exists_unconfirmed', organization: @org.name) %>
|
||||
</div>
|
||||
<% elsif result[:status] == :user_exists_and_in_org %>
|
||||
<div class="alert alert-info" role="alert">
|
||||
<strong><%= result[:email] %></strong>
|
||||
-
|
||||
<%= t('invite_users.results.user_exists_and_in_org', organization: @org.name, role: t("user_organizations.enums.role.#{result[:user_org].role}")) %>
|
||||
</div>
|
||||
<% elsif result[:status] == :user_exists_invited_to_org %>
|
||||
<div class="alert alert-info" role="alert">
|
||||
<strong><%= result[:email] %></strong>
|
||||
-
|
||||
<%= t('invite_users.results.user_exists_invited_to_org', organization: @org.name, role: t("user_organizations.enums.role.#{result[:user_org].role}")) %>
|
||||
</div>
|
||||
<% elsif result[:status] == :user_created %>
|
||||
<div class="alert alert-success" role="alert">
|
||||
<strong><%= result[:email] %></strong>
|
||||
-
|
||||
<%= t('invite_users.results.user_created') %>
|
||||
</div>
|
||||
<% elsif result[:status] == :user_created_invited_to_org %>
|
||||
<div class="alert alert-success" role="alert">
|
||||
<strong><%= result[:email] %></strong>
|
||||
-
|
||||
<%= t('invite_users.results.user_created_invited_to_org', organization: @org.name, role: t("user_organizations.enums.role.#{result[:user_org].role}")) %>
|
||||
</div>
|
||||
<% elsif result[:status] == :user_invalid %>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<strong><%= result[:email] %></strong>
|
||||
-
|
||||
<%= t('invite_users.results.user_invalid') %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @too_many_emails %>
|
||||
<%= t('invite_users.results.too_many_emails', nr: Constants::INVITE_USERS_LIMIT) %>
|
||||
<% end %>
|
||||
</div>
|
|
@ -181,7 +181,7 @@
|
|||
aria-expanded="false">
|
||||
<span class="glyphicon glyphicon-info-sign"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<ul class="dropdown-menu" data-hook="navigation-help-menu">
|
||||
<li><%= link_to t('nav.help.tutorials'),
|
||||
Constants::TUTORIALS_URL,
|
||||
target: "_blank" %></li>
|
||||
|
@ -217,7 +217,7 @@
|
|||
<%= image_tag avatar_path(current_user, :icon_small),
|
||||
class: "avatar" %>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<ul class="dropdown-menu" data-hook="navigation-user-menu">
|
||||
<li>
|
||||
<%= link_to t('nav.user.profile'), edit_user_registration_path %>
|
||||
</li>
|
||||
|
|
|
@ -67,10 +67,10 @@
|
|||
<%= t("users.settings.organizations.edit.manage_users") %>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<%= link_to "#", class: "btn btn-primary", data: { toggle: "modal", target: "#add-user-modal" } do %>
|
||||
<a href="#" class="btn btn-primary" data-trigger="invite-users" data-modal-id="org-invite-users-modal">
|
||||
<span class="glyphicon glyphicon-plus"></span>
|
||||
<%= t("users.settings.organizations.edit.add_user") %>
|
||||
<% end %>
|
||||
</a>
|
||||
<div class="users-datatable">
|
||||
<table id="users-table" class="table" data-source="<%= organization_users_datatable_path(@org, format: :json) %>">
|
||||
<thead>
|
||||
|
@ -95,7 +95,15 @@
|
|||
|
||||
<%= render partial: "users/settings/organizations/name_modal.html.erb" %>
|
||||
<%= render partial: "users/settings/organizations/description_modal.html.erb" %>
|
||||
<%= render partial: "users/settings/organizations/add_user_modal.html.erb", locals: { org: @org } %>
|
||||
<%= render(
|
||||
partial: 'shared/invite_users_modal.html.erb',
|
||||
locals: {
|
||||
modal_id: 'org-invite-users-modal',
|
||||
type: 'invite_to_org',
|
||||
organization: @org
|
||||
}
|
||||
)
|
||||
%>
|
||||
<%= render partial: "users/settings/organizations/destroy_modal.html.erb", locals: { org: @org } %>
|
||||
<%= render partial: "users/settings/organizations/destroy_user_organization_modal.html.erb" %>
|
||||
<%= stylesheet_link_tag 'datatables' %>
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
<div class="modal" id="add-user-modal" tabindex="-1" role="dialog" aria-labelledby="add-user-modal-label">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">
|
||||
<%= t("users.settings.organizations.edit.modal_add_user.title") %>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="panel-group" id="invite-accordion" role="tablist" aria-multiselectable="true">
|
||||
|
||||
<!-- Invite existing user panel -->
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="invite-existing-heading">
|
||||
<h4 class="panel-title">
|
||||
<a role="button" data-toggle="collapse" data-parent="#invite-accordion" href="#invite-existing" aria-expanded="true" aria-controls="invite-existing">
|
||||
<%= t("users.settings.organizations.edit.modal_add_user.existing_heading") %>
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="invite-existing" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="invite-existing-heading">
|
||||
<div class="panel-body">
|
||||
<%= bootstrap_form_tag url: search_organization_users_path(org, format: :json), remote: true, method: :get, data: { id: "invite-existing-form" } do |f| %>
|
||||
<div class="form-group">
|
||||
<label for="existing_query">
|
||||
<%= t("users.settings.organizations.edit.modal_add_user.existing_label") %>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="text" placeholder="<%= t("users.settings.organizations.edit.modal_add_user.existing_placeholder") %>" value="" name="[existing_query]" id="existing_query">
|
||||
<span class="input-group-btn">
|
||||
<%= f.submit t("general.search"), class: "btn btn-primary" %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div id="invite-existing-results"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Invite new user panel -->
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="invite-new-heading">
|
||||
<h4 class="panel-title">
|
||||
<a class="collapsed" role="button" data-toggle="collapse" data-parent="#invite-accordion" href="#invite-new" aria-expanded="false" aria-controls="invite-new">
|
||||
<%= t("users.settings.organizations.edit.modal_add_user.new_heading") %>
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="invite-new" class="panel-collapse collapse" role="tabpanel" aria-labelledby="invite-new-heading">
|
||||
<div class="panel-body">
|
||||
<%= bootstrap_form_for User.new, url: create_user_and_user_organization_path(format: :json), remote: true, data: { id: "invite-new-form" } do |f| %>
|
||||
<input type="hidden" name="organization_id" id="organization_id" value="<%= org.id %>">
|
||||
<input type="hidden" data-id="new-user-role-input" name="role" id="role" value="">
|
||||
<%= f.text_field :full_name, label: t("users.settings.organizations.edit.modal_add_user.new_label_name"), placeholder: t("users.settings.organizations.edit.modal_add_user.new_placeholder_name"), data: { id: "invite-new-name-input", role: "input" } %>
|
||||
<%= f.text_field :email, label: t("users.settings.organizations.edit.modal_add_user.new_label_email"), placeholder: t("users.settings.organizations.edit.modal_add_user.new_placeholder_email"), data: { id: "invite-new-email-input", role: "input" } %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><%= t("general.cancel") %></button>
|
||||
|
||||
<!-- Invite button -->
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" data-id="invite-btn" aria-haspopup="true" aria-expanded="false" disabled="disabled">
|
||||
<%= t("users.settings.organizations.edit.modal_add_user.invite") %>
|
||||
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#" data-action="invite" data-value="guest">
|
||||
<%= t("users.settings.organizations.edit.modal_add_user.invite_guest") %>
|
||||
</a></li>
|
||||
<li><a href="#" data-action="invite" data-value="normal_user">
|
||||
<%= t("users.settings.organizations.edit.modal_add_user.invite_user") %>
|
||||
</a></li>
|
||||
<li><a href="#" data-action="invite" data-value="admin">
|
||||
<%= t("users.settings.organizations.edit.modal_add_user.invite_admin") %>
|
||||
</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,36 +0,0 @@
|
|||
<% if users.count > 0 %>
|
||||
<%= bootstrap_form_for UserOrganization.new, url: create_user_organization_path, data: { id: "create-user-organization-form" } do %>
|
||||
<input type="hidden" name="user_organization[organization_id]" id="user_organization_organization_id" value="<%= org.id %>">
|
||||
<input type="hidden" data-id="existing-user-id-input" name="user_organization[user_id]" id="user_organization_user_id" value="">
|
||||
<input type="hidden" data-id="existing-user-role-input" name="user_organization[role]" id="user_organization_role" value="">
|
||||
<div class="btn-group-vertical btn-group-existing-users" data-toggle="buttons">
|
||||
<label class="btn btn-default btn-title">
|
||||
<strong><%= t("users.settings.organizations.edit.modal_add_user.existing_results_title") %></strong>
|
||||
</label>
|
||||
<% users.each do |user| %>
|
||||
<label class="btn btn-default">
|
||||
<input type="radio" name="options" data-action="select-existing-user" data-user-id="<%= user.id %>" data-id="user-result-<%= user.id %>" autocomplete="off">
|
||||
<%= highlight user.full_name, query.strip.split(/\s+/) %>
|
||||
</label>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if nr_of_members > 0 %>
|
||||
<div class="existing-users-smalltext"><em>
|
||||
<%= t("users.settings.organizations.edit.modal_add_user.existing_users_members_smalltext", count: nr_of_members) %>
|
||||
</em></div>
|
||||
<% end %>
|
||||
<% if nr_of_results > users.count %>
|
||||
<div class="existing-users-smalltext"><em>
|
||||
<%= t("users.settings.organizations.edit.modal_add_user.existing_users_smalltext", count: users.count) %>
|
||||
</em></div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else%>
|
||||
<em>
|
||||
<% if nr_of_members > 0 %>
|
||||
<%= t("users.settings.organizations.edit.modal_add_user.no_existing_users_members_smalltext", count: nr_of_members) %>
|
||||
<% else %>
|
||||
<%= t("users.settings.organizations.edit.modal_add_user.no_existing_users") %>
|
||||
<% end %>
|
||||
</em>
|
||||
<% end %>
|
|
@ -59,6 +59,7 @@ Rails.application.config.assets.precompile += %w(assets.js)
|
|||
Rails.application.config.assets.precompile += %w(comments.js)
|
||||
Rails.application.config.assets.precompile += %w(projects/show.js)
|
||||
Rails.application.config.assets.precompile += %w(notifications.js)
|
||||
Rails.application.config.assets.precompile += %w(users/invite_users_modal.js)
|
||||
|
||||
# Libraries needed for Handsontable formulas
|
||||
Rails.application.config.assets.precompile += %w(lodash.js)
|
||||
|
|
|
@ -41,6 +41,9 @@ class Constants
|
|||
# Activity limited query/display elements for pages
|
||||
ACTIVITY_AND_NOTIF_SEARCH_LIMIT = 10
|
||||
|
||||
# Maximum number of users that can be invited in a single action
|
||||
INVITE_USERS_LIMIT = 20
|
||||
|
||||
#=============================================================================
|
||||
# File and data memory size
|
||||
#=============================================================================
|
||||
|
@ -196,6 +199,9 @@ class Constants
|
|||
'text/plain'
|
||||
].freeze
|
||||
|
||||
# Very basic regex to check for validity of emails
|
||||
BASIC_EMAIL_REGEX = /^[^@]+@[^@]+\.[^@]+$/
|
||||
|
||||
# Organization name for default admin user
|
||||
DEFAULT_PRIVATE_ORG_NAME = 'My projects'.freeze
|
||||
|
||||
|
|
|
@ -1209,32 +1209,6 @@ en:
|
|||
can_delete_message: "This team can be deleted because it doesn't have any projects."
|
||||
delete_text: "Delete team."
|
||||
cannot_delete_message_projects: "Cannot delete this team. Only empty teams (without any projects) can be deleted."
|
||||
modal_add_user:
|
||||
title: "Invite user to team"
|
||||
existing_heading: "Invite existing sciNote user"
|
||||
existing_label: "Find existing user by name or email:"
|
||||
existing_placeholder: "Name or email"
|
||||
existing_results_title: "Choose user"
|
||||
no_existing_users: "No existing users found."
|
||||
existing_users_members_smalltext:
|
||||
one: "The search matched 1 user that is already member of the team."
|
||||
other: "The search matched %{count} users that are already members of the team."
|
||||
no_existing_users_members_smalltext:
|
||||
one: "No existing users that could be invited found; the search, however, matched 1 user that is already member of the team."
|
||||
other: "No existing users that could be invited found; the search, however, matched %{count} users that are already members of the team."
|
||||
existing_users_smalltext: "Only showing top %{count} matched results."
|
||||
existing_flash_success: "User %{user} successfully invited to team as %{role}."
|
||||
existing_flash_error: "Error inviting user to team."
|
||||
new_heading: "Invite new user"
|
||||
new_label_name: "Type in the new user's full name:"
|
||||
new_placeholder_name: "Full name"
|
||||
new_label_email: "Type in the new user's email:"
|
||||
new_placeholder_email: "Email"
|
||||
new_flash_success: "User %{user} successfully invited to team as %{role}. Confirmation email was sent to %{email}."
|
||||
invite: "Invite user"
|
||||
invite_guest: "as Guest"
|
||||
invite_user: "as Normal user"
|
||||
invite_admin: "as Administrator"
|
||||
modal_destroy_organization:
|
||||
title: "Delete team"
|
||||
message: "Are you sure you wish to delete team %{org}? All of the users will be removed from the team as well. This action is irreversible."
|
||||
|
@ -1449,6 +1423,30 @@ en:
|
|||
head_title: "Edit protocol"
|
||||
no_keywords: "No keywords"
|
||||
|
||||
invite_users:
|
||||
to_org:
|
||||
title: "Invite users to team %{organization}"
|
||||
heading: "Invite more people to team %{organization} and start using sciNote."
|
||||
no_org:
|
||||
title: "Invite users to sciNote"
|
||||
heading: "Invite more people to start using sciNote."
|
||||
input_subtitle: "Input one or multiple emails, confirm each email with ENTER key."
|
||||
invite_to_org_heading: "Invite users to my team:"
|
||||
invite_btn: "Invite user/s"
|
||||
invite_guest: "as Guest/s"
|
||||
invite_user: "as Normal user/s"
|
||||
invite_admin: "as Administrator/s"
|
||||
results:
|
||||
heading: "Invitation results:"
|
||||
user_exists: "User is already a member of sciNote."
|
||||
user_exists_unconfirmed: "User is already a member of sciNote but is not confirmed yet - cannot invite to team %{organization}."
|
||||
user_exists_and_in_org: "User is already a member of sciNote and team %{organization} as %{role}."
|
||||
user_exists_invited_to_org: "User was already a member of sciNote - successfully invited to team %{organization} as %{role}."
|
||||
user_created: "User succesfully invited to sciNote."
|
||||
user_created_invited_to_org: "User successfully invited to sciNote and team %{organization} as %{role}."
|
||||
user_invalid: "Invalid email."
|
||||
too_many_emails: "Only invited first %{nr} emails. To invite more users, "
|
||||
|
||||
time:
|
||||
formats:
|
||||
full: "%d.%m.%Y %H:%M"
|
||||
|
|
|
@ -29,16 +29,20 @@ Rails.application.routes.draw do
|
|||
put "users/settings/organizations/:organization_id", to: "users/settings#update_organization", as: "update_organization"
|
||||
get "users/settings/organizations/:organization_id/name", to: "users/settings#organization_name", as: "organization_name"
|
||||
get "users/settings/organizations/:organization_id/description", to: "users/settings#organization_description", as: "organization_description"
|
||||
get "users/settings/organizations/:organization_id/search", to: "users/settings#search_organization_users", as: "search_organization_users"
|
||||
post "users/settings/organizations/:organization_id/users_datatable", to: "users/settings#organization_users_datatable", as: "organization_users_datatable"
|
||||
delete "users/settings/organizations/:organization_id", to: "users/settings#destroy_organization", as: "destroy_organization"
|
||||
post "users/settings/user_organizations/new", to: "users/settings#create_user_organization", as: "create_user_organization"
|
||||
post "users/settings/users_organizations/new_user", to: "users/settings#create_user_and_user_organization", as: "create_user_and_user_organization"
|
||||
put "users/settings/user_organizations/:user_organization_id", to: "users/settings#update_user_organization", as: "update_user_organization"
|
||||
get "users/settings/user_organizations/:user_organization_id/leave_html", to: "users/settings#leave_user_organization_html", as: "leave_user_organization_html"
|
||||
get "users/settings/user_organizations/:user_organization_id/destroy_html", to: "users/settings#destroy_user_organization_html", as: "destroy_user_organization_html"
|
||||
delete "users/settings/user_organizations/:user_organization_id", to: "users/settings#destroy_user_organization", as: "destroy_user_organization"
|
||||
|
||||
# Invite users
|
||||
devise_scope :user do
|
||||
post '/invite',
|
||||
to: 'users/invitations#invite_users',
|
||||
as: 'invite_users'
|
||||
end
|
||||
|
||||
# Notifications
|
||||
get 'users/:id/recent_notifications',
|
||||
to: 'user_notifications#recent_notifications',
|
||||
|
|
Loading…
Reference in a new issue