Rework the team invite modal functionality on members page [SCI-5099]

This commit is contained in:
Martin Artnik 2021-07-05 12:39:54 +02:00
parent f835f88e64
commit 5f528fcdb5
8 changed files with 173 additions and 64 deletions

View file

@ -100,7 +100,7 @@ var dropdownSelector = (function() {
// Save data to the field
function updateCurrentData(container, data) {
container.find('.data-field').val(JSON.stringify(data));
container.find('.data-field').val(JSON.stringify(data)).change();
}
// Search filter for non-ajax data
@ -293,6 +293,7 @@ var dropdownSelector = (function() {
var optionContainer;
var perfectScroll;
var dropdownContainer;
var toggleElement;
if (selectElement.length === 0) return;
@ -316,6 +317,30 @@ var dropdownSelector = (function() {
`).appendTo(dropdownContainer);
// Blank option
if (selectElement.data('blank')) {
$(`<div class="dropdown-blank btn">${selectElement.data('blank')}</div>`)
.appendTo(dropdownContainer.find('.dropdown-container'))
.click(() => {
dropdownContainer.find('.dropdown-group, .dropdown-option').removeClass('select');
saveData(selectElement, dropdownContainer);
dropdownContainer.removeClass('open');
});
}
if (selectElement.data('toggle-target')) {
dropdownContainer.find('.data-field').on('change', function() {
toggleElement = $(selectElement.data('toggle-target'));
if (getCurrentData(dropdownContainer).length > 0) {
toggleElement.removeClass('hidden');
toggleElement.find('input, select').removeAttr('disabled');
} else {
toggleElement.addClass('hidden');
toggleElement.find('input, select').attr('disabled', true);
}
});
}
// If we setup Select All we draw it and add correspond logic
if (selectElement.data('select-all-button')) {
$(`<div class="dropdown-select-all btn">${selectElement.data('select-all-button')}</div>`)

View file

@ -26,6 +26,8 @@
var teamSelectorDropdown = modal.find('[data-role=team-selector-dropdown]');
var teamSelectorDropdown2 = $();
var emailsInput = modal.find('.emails-input');
var teamsInput = modal.find('.teams-input');
var roleInput = modal.find('.role-input');
var recaptchaErrorMsgDiv = modal.find('#recaptcha-error-msg');
var recaptchaErrorText = modal.find('#recaptcha-error-msg>span');
@ -46,7 +48,7 @@
labelHTML: true,
tagClass: 'users-dropdown-list',
inputTagMode: true,
customDropdownIcon: () => { return '<i class="fas fa-search pull-right"></i>'; },
customDropdownIcon: () => { return '<i class="fas fa-search right-icon"></i>'; },
onChange: () => {
let values = dropdownSelector.getValues(emailsInput);
if (values.length > 0) {
@ -114,6 +116,8 @@
modal.find('[data-action=invite]').off('click').on('click', function() {
var data = {
emails: dropdownSelector.getValues(emailsInput),
team_ids: dropdownSelector.getValues(teamsInput),
role: roleInput.val(),
'g-recaptcha-response': $('#recaptcha-invite-modal').val()
};
@ -121,24 +125,24 @@
switch (type) {
case 'invite_to_team':
data.teamId = modal.attr('data-team-id');
data.team_ids = [modal.attr('data-team-id')];
data.role = $(this).attr('data-team-role');
break;
case 'invite_to_team_with_role':
data.teamId = modal.attr('data-team-id');
data.team_ids = [modal.attr('data-team-id')];
data.role = modal.attr('data-team-role');
break;
case 'invite':
break;
case 'invite_with_team_selector':
if (teamSelectorCheckbox.is(':checked')) {
data.teamId = teamSelectorDropdown.val();
data.team_ids = [teamSelectorDropdown.val()];
data.role = $(this).attr('data-team-role');
}
break;
case 'invite_with_team_selector_and_role':
if (teamSelectorCheckbox.is(':checked')) {
data.teamId = teamSelectorDropdown.val();
data.team_ids = [teamSelectorDropdown.val()];
data.role = modal.attr('data-team-role');
}
break;
@ -181,6 +185,11 @@
script.src = 'https://www.google.com/recaptcha/api.js?hl=en';
$(script).insertAfter('#recaptcha-service');
// Remove 'data-invited="true"' status
dropdownSelector.init(teamsInput, {
optionClass: 'checkbox-icon'
});
modal.removeAttr('data-invited');
}).on('hide.bs.modal', function() {
// 'Reset' modal state

View file

@ -3,6 +3,19 @@
@import "constants";
.select-container--with-search .dropdown-selector-container {
&.active,
&.open {
.right-icon.fa-search {
display: block;
}
.right-icon.fa-caret-down {
display: none;
}
}
}
.dropdown-selector-container {
display: inline-block;
float: left;
@ -163,6 +176,18 @@
z-index: 5;
}
.dropdown-blank {
border-radius: 0;
padding-left: 7px;
text-align: left;
width: 100%;
&:hover {
background: $brand-primary;
color: $color-white;
}
}
.dropdown-option {
align-items: center;
cursor: pointer;

View file

@ -1319,6 +1319,7 @@ body > .loading-overlay {
.modal-invite-users {
.bootstrap-tagsinput {
min-width: 450px;
width: 100%;
}
.results-container .alert {
@ -1342,6 +1343,10 @@ body > .loading-overlay {
}
}
.dropdown-selector-container {
margin-bottom: 20px;
}
.g-recaptcha {
margin-top: 20px;
}
@ -1486,3 +1491,15 @@ a.disabled-with-click-events {
}
}
.form-select {
border: 1px solid $color-silver-chalice;
border-radius: 4px;
color: $color-emperor;
display: block;
font-size: 14px;
min-height: 36px;
outline: 0;
padding: 8px 42px 3px 3px;
width: 100%;
}

View file

@ -100,46 +100,49 @@ module Users
user.delay.deliver_invitation
end
if @team && user
user_team = UserTeam.find_by_user_id_and_team_id(user.id, @team.id)
if user_team
result[:status] = :user_exists_and_in_team
else
# Also generate user team relation
user_team = UserTeam.new(
user: user,
team: @team,
role: @role
)
user_team.save
if @teams.any? && user
@teams.each do |team|
user_team = UserTeam.find_by(user_id: user.id, team_id: team.id)
if user_team
result[:status] = :user_exists_and_in_team
else
# Also generate user team relation
user_team = UserTeam.new(
user: user,
team: team,
role: @role || 'normal_user'
)
user_team.save
generate_notification(
@user,
user,
user_team.team,
user_team.role_str
)
Activities::CreateActivityService
.call(activity_type: :invite_user_to_team,
owner: current_user,
subject: @team,
team: @team,
message_items: {
team: @team.id,
user_invited: user.id,
role: user_team.role_str
})
generate_notification(
@user,
user,
user_team.team,
user_team.role_str
)
result[:status] = if result[:status] == :user_exists && !user.confirmed?
:user_exists_unconfirmed_invited_to_team
elsif result[:status] == :user_exists
:user_exists_invited_to_team
else
:user_created_invited_to_team
end
Activities::CreateActivityService
.call(activity_type: :invite_user_to_team,
owner: current_user,
subject: team,
team: team,
message_items: {
team: team.id,
user_invited: user.id,
role: user_team.role_str
})
result[:status] = if result[:status] == :user_exists && !user.confirmed?
:user_exists_unconfirmed_invited_to_team
elsif result[:status] == :user_exists
:user_exists_invited_to_team
else
:user_created_invited_to_team
end
end
result[:user_teams] << user_team
end
result[:user_team] = user_team
end
@invite_results << result
@ -156,6 +159,17 @@ module Users
end
end
def invitable_teams
teams = current_user.teams
.select(:id, :name)
.joins(:user_teams)
.where('user_teams.role': UserTeam.roles[:admin])
.distinct
.select { |team| can_invite_team_users?(team) }
render json: teams.map { |t| { value: t.id, label: t.name } }.to_json
end
private
def update_sanitized_params
@ -185,12 +199,13 @@ module Users
def check_invite_users_permission
@user = current_user
@emails = params[:emails]&.map(&:downcase)
@team = Team.find_by_id(params['teamId'])
@teams = Team.where(id: params[:team_ids]).select { |team| can_manage_team_users?(team) }
return render_403 if @teams.none?
@role = params['role']
return render_403 if @team && @role.nil? # if we select team, we must select role
return render_403 if @emails.blank? # We must have at least one email
return render_403 if @team && !can_manage_team_users?(@team) # if we select team, we must check permission
return render_403 if @role && !UserTeam.roles.key?(@role) # if we select role, we must check that this role exist
end
end

View file

@ -74,29 +74,38 @@ invite_to_team = type.in?(%w(invite_to_team invite_to_team_with_role))
<div class="user-selector">
<select class="emails-input" multiple data-role="tags-input" name="emails[]">
</select>
<br />
<label><%= t('invite_users.input_subtitle') %></label>
<h4><%= t('invite_users.input_subtitle') %></h4>
</div>
<% if current_user && type.in?(['invite_with_team_selector', 'invite_with_team_selector_and_role']) %>
<% # Only allow inviting to teams where user is admin %>
<% teams = current_user.teams
.joins(:user_teams)
.where('user_teams.role': UserTeam.roles[:admin])
.select { |team| can_invite_team_users?(team) } %>
<% if teams.any? %>
<div class="team-selector">
<div class="heading">
<input type="checkbox" data-role="team-selector-checkbox" />
<span><%= t('invite_users.invite_to_team_heading') %></span>
<div class="team-selector">
<h4 class="heading">
<span><%= t('invite_users.select_team') %></span>
</h4>
<div class="team-selector filter-block small-left">
<div class="select-container select-container--with-search select-container--with-blank">
<select class="teams-input" name="teams[]"
data-placeholder = "<%= t('invite_users.select_team_blank') %>"
data-blank = "<%= t('invite_users.select_team_blank') %>"
data-select-all = "false"
data-toggle-target = "#role-select-container"
data-ajax-url = "<%= invitable_teams_path %>"
></select>
</div>
<div id="role-select-container" class="hidden">
<h4 class="heading">
<span><%= t('invite_users.select_team_role') %></span>
</h4>
<%= select_tag "role",
options_for_select(UserTeam.roles.keys
.map { |k| [k.humanize + (k == 'normal_user' ? " (#{t('invite_users.default')})" : ''), k] }, 'normal_user'),
disabled: true, class: "role-input from-control form-select"
%>
</div>
<%= select_tag('team-select',
options_for_select(teams.pluck(:name, :id)),
{ class: 'form-control selectpicker',
'data-role' => 'team-selector-dropdown',
disabled: 'disabled' }) %>
</div>
<% end %>
</div>
<% end %>
<% if ENV['ENABLE_RECAPTCHA'] == 'true' %>
<div id="recaptcha-service" class="g-recaptcha"

View file

@ -2209,12 +2209,18 @@ en:
no_team:
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."
input_subtitle: "You can enter one or more emails. To confirm each one press the ENTER key."
invite_to_team_heading: "Invite users to my team:"
invite_btn: "Invite members"
invite_guest: "As Guests"
invite_user: "As Normal Users"
invite_admin: "As Administrators"
new_member_email: "New member email"
select_team: "Select a team"
select_team_blank: "Do not add this user to any team"
select_team_role: "Select a team role"
default: "default"
errors:
recaptcha: "reCAPTCHA verification failed, please try again"
results:

View file

@ -109,6 +109,9 @@ Rails.application.routes.draw do
post '/invite',
to: 'users/invitations#invite_users',
as: 'invite_users'
get '/invitable_teams',
to: 'users/invitations#invitable_teams',
as: 'invitable_teams'
end
# Notifications