mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 14:45:56 +08:00
Rework the team invite modal functionality on members page [SCI-5099]
This commit is contained in:
parent
f835f88e64
commit
5f528fcdb5
|
@ -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>`)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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%;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue