Merge pull request #2138 from Matt-Yorkley/email/manager_invite

Email/manager invite
This commit is contained in:
Pau Pérez Fabregat
2018-03-23 15:27:14 +01:00
committed by GitHub
15 changed files with 246 additions and 12 deletions

View File

@@ -1,5 +1,5 @@
angular.module("admin.enterprises")
.controller "enterpriseCtrl", ($scope, $window, NavigationCheck, enterprise, EnterprisePaymentMethods, EnterpriseShippingMethods, SideMenu, StatusMessage) ->
.controller "enterpriseCtrl", ($scope, $http, $window, NavigationCheck, enterprise, EnterprisePaymentMethods, EnterpriseShippingMethods, SideMenu, StatusMessage) ->
$scope.Enterprise = enterprise
$scope.PaymentMethods = EnterprisePaymentMethods.paymentMethods
$scope.ShippingMethods = EnterpriseShippingMethods.shippingMethods
@@ -26,7 +26,7 @@ angular.module("admin.enterprises")
# from a directive "nav-check" in the page - if we pass it here it will be called in the test suite,
# and on all new uses of this contoller, and we might not want that.
enterpriseNavCallback = ->
if $scope.enterprise_form.$dirty
if $scope.enterprise_form != undefined && $scope.enterprise_form.$dirty
t('admin.unsaved_confirm_leave')
# Register the NavigationCheck callback
@@ -51,3 +51,16 @@ angular.module("admin.enterprises")
$scope.enterprise_form?.$setDirty()
else
alert ("#{manager.email}" + " " + t("is_already_manager"))
$scope.inviteManager = ->
$scope.invite_errors = $scope.invite_success = null
email = $scope.newUser
$http.post("/admin/manager_invitations", {email: email, enterprise_id: $scope.Enterprise.id}).success (data)->
$scope.addManager({id: data.user, email: email})
$scope.invite_success = t('user_invited', email: email)
.error (data) ->
$scope.invite_errors = data.errors
$scope.resetModal = ->
$scope.newUser = $scope.invite_errors = $scope.invite_success = null

View File

@@ -0,0 +1,19 @@
#invite-manager-modal{ng: {app: 'admin.enterprises', controller: 'enterpriseCtrl'}}
.margin-bottom-30.text-center
.text-big
= t('js.admin.modals.invite_title')
%p.alert-box.ok{ng: {show: 'invite_success'}}
{{invite_success}}
%p.alert-box.error{ng: {show: 'invite_errors'}}
{{invite_errors}}
%input#invite_email.fullwidth.margin-bottom-20{ng: {model: 'newUser'}}
.margin-bottom-20.text-center
%button.text-center.margin-top-10{ng: {show: '!invite_success', click: 'inviteManager()'}}
= t('js.admin.modals.invite')
%button.text-center.margin-top-10{ng: {show: 'invite_success', click: 'resetModal(); close()'}}
= t('js.admin.modals.close')

View File

@@ -1,3 +1,7 @@
input[type="submit"], input[type="button"], button, .button {
cursor: pointer;
}
.text-center {
text-align: center;
}

View File

@@ -0,0 +1,39 @@
module Admin
class ManagerInvitationsController < Spree::Admin::BaseController
def create
@email = params[:email]
@enterprise = Enterprise.find(params[:enterprise_id])
authorize! :edit, @enterprise
existing_user = Spree::User.find_by_email(@email)
if existing_user
render json: { errors: t('admin.enterprises.invite_manager.user_already_exists') }, status: :unprocessable_entity
return
end
new_user = create_new_manager
if new_user
render json: { user: new_user.id }, status: :ok
else
render json: { errors: t('admin.enterprises.invite_manager.error') }, status: 500
end
end
private
def create_new_manager
password = Devise.friendly_token
new_user = Spree::User.create(email: @email, unconfirmed_email: @email, password: password)
new_user.reset_password_token = Devise.friendly_token
new_user.save!
@enterprise.users << new_user
Delayed::Job.enqueue ManagerInvitationJob.new(@enterprise.id, new_user.id)
new_user
end
end
end

View File

@@ -38,6 +38,10 @@ class UserConfirmationsController < DeviseController
'not_confirmed'
end
if resource.reset_password_token.present?
return spree.edit_spree_user_password_path(reset_password_token: resource.reset_password_token)
end
path = (session[:confirmation_return_url] || login_path).to_s
path += path.include?('?') ? '&' : '?'
path + "validation=#{result}"

View File

@@ -0,0 +1,7 @@
ManagerInvitationJob = Struct.new(:enterprise_id, :user_id) do
def perform
enterprise = Enterprise.find enterprise_id
user = Spree::User.find user_id
EnterpriseMailer.manager_invitation(enterprise, user).deliver
end
end

View File

@@ -12,6 +12,18 @@ class EnterpriseMailer < Spree::BaseMailer
:subject => subject)
end
def manager_invitation(enterprise, user)
@enterprise = enterprise
@instance = Spree::Config[:site_name]
@instance_email = from_address
subject = t('enterprise_mailer.invite_manager.subject', enterprise: @enterprise.name)
mail(to: user.email,
from: from_address,
subject: subject)
end
private
def find_enterprise(enterprise)

View File

@@ -60,3 +60,16 @@
- @enterprise.users.each do |manager|
= manager.email
%br
- if full_permissions
%form
.row
.three.columns.alpha
%label
= t('.invite_manager')
%div{'ofn-with-tip' => t('.invite_manager_tip')}
%a= t('admin.whats_this')
.eight.columns.omega
.row
%a.button.help-modal{template: 'admin/modals/invite_manager.html'}
= t('.add_unregistered_user')

View File

@@ -0,0 +1,16 @@
%h3
= t('invite_email.greeting')
%p.lead
= t('invite_email.invited_to_manage', enterprise: @enterprise.name, instance: @instance)
%p
= t('invite_email.confirm_your_email')
%p
= t('invite_email.mistakenly_sent', owner_email: @enterprise.owner.email, instance: @instance, instance_email: @instance_email)
%p
= t :email_help
= render 'shared/mailers/signoff'
= render 'shared/mailers/social_and_contact'

View File

@@ -114,6 +114,8 @@ en:
subject: "Please confirm the email address for %{enterprise}"
welcome:
subject: "%{enterprise} is now on %{sitename}"
invite_manager:
subject: "%{enterprise} has invited you to be a manager"
producer_mailer:
order_cycle:
subject: "Order cycle report for %{producer}"
@@ -665,6 +667,9 @@ en:
notifications_note: 'Note: A new email address may need to be confirmed prior to use'
managers: Managers
managers_tip: The other users with permission to manage this enterprise.
invite_manager: "Invite Manager"
invite_manager_tip: "Invite an unregistered user to sign up and become a manager of this enterprise."
add_unregistered_user: "Add an unregistered user"
email_confirmed: "Email confirmed"
email_not_confirmed: "Email not confirmed"
actions:
@@ -724,6 +729,9 @@ en:
welcome_text: You have successfully created a
next_step: Next step
choose_starting_point: 'Choose your starting point:'
invite_manager:
user_already_exists: "User already exists"
error: "Something went wrong"
order_cycles:
edit:
advanced_settings: Advanced Settings
@@ -1319,6 +1327,13 @@ See the %{link} to find out more about %{sitename}'s features and to start using
If you are a producer or food enterprise, we are excited to have you as a part of the network."
email_signup_help_html: "We welcome all your questions and feedback; you can use the <em>Send Feedback</em> button on the site or email us at %{email}"
invite_email:
greeting: "Hello!"
invited_to_manage: "You have been invited to manage %{enterprise} on %{instance}."
confirm_your_email: "You will receive an email shortly to confirm your registration."
set_a_password: "You will then be prompted to set a password before you are able to administer the enterprise."
mistakenly_sent: "Not sure why you have received this email? Please contact %{owner_email} for more information, or you can contact %{instance} at %{instance_email}."
producer_mail_greeting: "Dear"
producer_mail_text_before: "We now have all the consumer orders for the next food drop."
producer_mail_order_text: "Here is a summary of the orders for your products:"
@@ -2133,10 +2148,10 @@ See the %{link} to find out more about %{sitename}'s features and to start using
unsaved_changes_confirmation: "Unsaved changes will be lost. Continue anyway?"
one_product_unsaved: "Changes to one product remain unsaved."
products_unsaved: "Changes to %{n} products remain unsaved."
add_manager: "Add a manager"
is_already_manager: "is already a manager!"
no_change_to_save: " No change to save"
add_manager: "Add a manager"
user_invited: "%{email} has been invited to manage this enterprise"
add_manager: "Add an existing user"
users: "Users"
about: "About"
images: "Images"
@@ -2219,6 +2234,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using
enterprise_limit_reached: "You have reached the standard limit of enterprises per account. Write to %{contact_email} if you need to increase it."
modals:
got_it: Got it
close: "Close"
invite: "Invite"
invite_title: "Invite an unregistered user"
tag_rule_help:
title: Tag Rules
overview: Overview

View File

@@ -113,6 +113,8 @@ Openfoodnetwork::Application.routes.draw do
resources :tag_rules, only: [:destroy]
end
resources :manager_invitations, only: [:create]
resources :enterprise_relationships
resources :enterprise_roles

View File

@@ -0,0 +1,42 @@
require 'spec_helper'
module Admin
describe ManagerInvitationsController, type: :controller do
let!(:existing_user) { create(:user) }
let!(:enterprise) { create(:enterprise) }
let(:admin) { create(:admin_user) }
describe "#create" do
context "when given email matches an existing user" do
before do
controller.stub spree_current_user: admin
end
it "returns an error" do
spree_post :create, {email: existing_user.email, enterprise_id: enterprise.id}
expect(response.status).to eq 422
expect(json_response['errors']).to eq I18n.t('admin.enterprises.invite_manager.user_already_exists')
end
end
context "signing up a new user" do
before do
controller.stub spree_current_user: admin
end
it "creates a new user, sends an invitation email, and returns the user id" do
expect do
spree_post :create, {email: 'un.registered@email.com', enterprise_id: enterprise.id}
end.to enqueue_job Delayed::PerformableMethod
new_user = Spree::User.find_by_email('un.registered@email.com')
expect(new_user.reset_password_token).to_not be_nil
expect(response.status).to eq 200
expect(json_response['user']).to eq new_user.id
end
end
end
end
end

View File

@@ -25,17 +25,17 @@ describe UserConfirmationsController, type: :controller do
end
context "that has not been confirmed" do
it "redirects the user to login" do
spree_get :show, confirmation_token: unconfirmed_user.confirmation_token
expect(response).to redirect_to login_path(validation: 'confirmed')
end
it "confirms the user" do
spree_get :show, confirmation_token: unconfirmed_user.confirmation_token
expect(unconfirmed_user.reload.confirmed_at).not_to eq(nil)
end
it "redirects to previous url" do
it "redirects the user to #/login by default" do
spree_get :show, confirmation_token: unconfirmed_user.confirmation_token
expect(response).to redirect_to login_path(validation: 'confirmed')
end
it "redirects to previous url, if present" do
session[:confirmation_return_url] = producers_path + '#/login'
spree_get :show, confirmation_token: unconfirmed_user.confirmation_token
expect(response).to redirect_to producers_path + '#/login?validation=confirmed'
@@ -46,6 +46,13 @@ describe UserConfirmationsController, type: :controller do
spree_get :show, confirmation_token: unconfirmed_user.confirmation_token
expect(response).to redirect_to registration_path + '#/signup?after_login=%2Fregister&validation=confirmed'
end
it "redirects to set password page, if user needs to reset their password" do
unconfirmed_user.reset_password_token = Devise.friendly_token
unconfirmed_user.save!
spree_get :show, confirmation_token: unconfirmed_user.confirmation_token
expect(response).to redirect_to spree.edit_spree_user_password_path(reset_password_token: unconfirmed_user.reset_password_token)
end
end
end

View File

@@ -83,6 +83,7 @@ feature %q{
let!(:user1) { create(:user, email: 'user1@example.com') }
let!(:user2) { create(:user, email: 'user2@example.com') }
let!(:user3) { create(:user, email: 'user3@example.com', confirmed_at: nil) }
let(:new_email) { 'new@manager.com' }
let!(:enterprise) { create(:enterprise, name: 'Test Enterprise', owner: user1) }
let!(:enterprise_role) { create(:enterprise_role, user_id: user2.id, enterprise_id: enterprise.id) }
@@ -114,7 +115,9 @@ feature %q{
# user3 has been added and has an unconfirmed email address
expect(page).to have_css "tr#manager-#{user3.id}"
expect(page).to have_css 'i.unconfirmed'
within "tr#manager-#{user3.id}" do
expect(page).to have_css 'i.unconfirmed'
end
end
end
@@ -133,6 +136,29 @@ feature %q{
end
end
end
it "can invite unregistered users to be managers" do
find('a.button.help-modal').click
expect(page).to have_css '#invite-manager-modal'
within '#invite-manager-modal' do
fill_in 'invite_email', with: new_email
click_button I18n.t('js.admin.modals.invite')
expect(page).to have_content I18n.t('user_invited', email: new_email)
click_button I18n.t('js.admin.modals.close')
end
new_user = Spree::User.find_by_email_and_confirmed_at(new_email, nil)
expect(Enterprise.managed_by(new_user)).to include enterprise
within 'table.managers' do
expect(page).to have_content new_email
within "tr#manager-#{new_user.id}" do
expect(page).to have_css 'i.unconfirmed'
end
end
end
end
end

View File

@@ -2,17 +2,29 @@ require 'spec_helper'
describe EnterpriseMailer do
let!(:enterprise) { create(:enterprise) }
let!(:user) { create(:user) }
before do
ActionMailer::Base.deliveries = []
Spree::MailMethod.create!(environment: 'test')
end
it "sends a welcome email when given an enterprise" do
describe "#welcome" do
it "sends a welcome email when given an enterprise" do
EnterpriseMailer.welcome(enterprise).deliver
mail = ActionMailer::Base.deliveries.first
expect(mail.subject)
.to eq "#{enterprise.name} is now on #{Spree::Config[:site_name]}"
end
end
describe "#manager_invitation" do
it "should send a manager invitation email when given an enterprise and user" do
EnterpriseMailer.manager_invitation(enterprise, user).deliver
expect(ActionMailer::Base.deliveries.count).to eq 1
mail = ActionMailer::Base.deliveries.first
expect(mail.subject).to eq "#{enterprise.name} has invited you to be a manager"
end
end
end