From ccf893261169046b6c66762d263d7971edff04ce Mon Sep 17 00:00:00 2001 From: Luka Murn Date: Thu, 3 Nov 2016 11:27:17 +0100 Subject: [PATCH] Add first version of invite users modal --- .../sitewide/invite_users_modal.js | 164 ++++++++++++++++++ app/assets/stylesheets/themes/scinote.scss | 16 ++ .../users/invitations_controller.rb | 4 + app/views/shared/_invite_users_modal.html.erb | 129 ++++++++++++++ config/initializers/assets.rb | 1 + config/locales/en.yml | 14 ++ config/routes.rb | 5 + 7 files changed, 333 insertions(+) create mode 100644 app/assets/javascripts/sitewide/invite_users_modal.js create mode 100644 app/views/shared/_invite_users_modal.html.erb diff --git a/app/assets/javascripts/sitewide/invite_users_modal.js b/app/assets/javascripts/sitewide/invite_users_modal.js new file mode 100644 index 000000000..183b4b6b6 --- /dev/null +++ b/app/assets/javascripts/sitewide/invite_users_modal.js @@ -0,0 +1,164 @@ +(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 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=tagsinput]'); + + 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 + $('[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) { + } + }); + }); + }) + .on('shown.bs.modal', function() { + tagsInput.tagsinput('focus'); + }) + .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'); + }); + } + + 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(); + }); +})(); diff --git a/app/assets/stylesheets/themes/scinote.scss b/app/assets/stylesheets/themes/scinote.scss index dd40ded79..6b851acb0 100644 --- a/app/assets/stylesheets/themes/scinote.scss +++ b/app/assets/stylesheets/themes/scinote.scss @@ -1496,3 +1496,19 @@ html.turbolinks-progress-bar::before { width: 100px; } } + +.modal-invite-users { + .bootstrap-tagsinput { + min-width: 450px; + } + + .org-selector .heading { + margin-top: 15px; + margin-bottom: 5px; + + input[type=checkbox] { + vertical-align: middle; + margin: 0; + } + } +} diff --git a/app/controllers/users/invitations_controller.rb b/app/controllers/users/invitations_controller.rb index ff1e7b417..c8be3d46e 100644 --- a/app/controllers/users/invitations_controller.rb +++ b/app/controllers/users/invitations_controller.rb @@ -27,4 +27,8 @@ class Users::InvitationsController < Devise::InvitationsController resource end + + def invite_users + # TODO + end end diff --git a/app/views/shared/_invite_users_modal.html.erb b/app/views/shared/_invite_users_modal.html.erb new file mode 100644 index 000000000..f8f2b0705 --- /dev/null +++ b/app/views/shared/_invite_users_modal.html.erb @@ -0,0 +1,129 @@ +<% +# How to use this modal: +# 1. Render it in the page (HTML) of your choice +# 2. Add an element (e.g. ) 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 +%> + diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index f1a105b3b..55f62d834 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -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) diff --git a/config/locales/en.yml b/config/locales/en.yml index 4d49ceab0..e807aef08 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1449,6 +1449,20 @@ 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" + time: formats: full: "%d.%m.%Y %H:%M" diff --git a/config/routes.rb b/config/routes.rb index d1aca0457..f5dc7a41b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -39,6 +39,11 @@ Rails.application.routes.draw do 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 + post 'users/invite', + to: 'users/invitations#invite_users', + as: 'invite_users' + # Notifications get 'users/:id/recent_notifications', to: 'user_notifications#recent_notifications',