Merge pull request #8808 from Matt-Yorkley/login-modal

Remove all Angular code from Login/Signup process
This commit is contained in:
Filipe
2022-02-03 19:36:44 +00:00
committed by GitHub
54 changed files with 509 additions and 500 deletions

View File

@@ -56,7 +56,8 @@
#= require_tree .
document.addEventListener "turbo:load", ->
window.injector = angular.bootstrap document.body, ["Darkswarm"]
try
window.injector = angular.bootstrap document.body, ["Darkswarm"]
true
document.addEventListener "turbo:before-render", ->

View File

@@ -1,19 +0,0 @@
angular.module('Darkswarm').controller "ForgotCtrl", ($scope, $http, $location, AuthenticationService) ->
$scope.path = "/forgot"
$scope.sent = false
$scope.submit = ->
if $scope.spree_user.email != null
$http.post("/user/spree_user/password", {spree_user: $scope.spree_user}).then (response)->
$scope.sent = true
.catch (response) ->
$scope.errors = response.data.error
$scope.user_unconfirmed = (response.status == 401)
else
$scope.errors = t 'email_required'
$scope.resend_confirmation = ->
$http.post("/user/spree_user/confirmation", {spree_user: $scope.spree_user, return_url: $location.absUrl()}).then (response)->
$scope.messages = t('devise.confirmations.send_instructions')
.catch (response) ->
$scope.errors = t('devise.confirmations.failed_to_send')

View File

@@ -1,36 +0,0 @@
angular.module('Darkswarm').controller "LoginCtrl", ($scope, $timeout, $location, $http, $window, AuthenticationService, Redirections, Loading) ->
$scope.path = "/login"
$scope.modalMessage = null
$scope.$watch (->
AuthenticationService.modalMessage
), (newValue) ->
$scope.errors = newValue
$scope.submit = ->
Loading.message = t 'logging_in'
$http.post("/user/spree_user/sign_in", {spree_user: $scope.spree_user}).then (response)->
if window._paq
window._paq.push(['trackEvent', 'Signin/Signup', 'Login Submit Success', $location.absUrl()]);
if Redirections.after_login
$window.location.href = $window.location.origin + Redirections.after_login
else
$window.location.href = $window.location.origin + $window.location.pathname # Strips out hash fragments
.catch (response) ->
Loading.clear()
$scope.errors = response.data.message || response.data.error
$scope.user_unconfirmed = (response.data.error == t('devise.failure.unconfirmed'))
$scope.resend_confirmation = ->
$http.post("/user/spree_user/confirmation", {spree_user: $scope.spree_user, return_url: $location.absUrl()}).then (response)->
$scope.messages = t('devise.confirmations.send_instructions')
.catch (response) ->
$scope.errors = t('devise.confirmations.failed_to_send')
$timeout ->
if angular.isDefined($location.search()['validation'])
if $location.search()['validation'] == 'confirmed'
$scope.messages = t('devise.confirmations.confirmed')
if $location.search()['validation'] == 'not_confirmed'
$scope.errors = t('devise.confirmations.not_confirmed')

View File

@@ -1,17 +0,0 @@
angular.module('Darkswarm').controller "SignupCtrl", ($scope, $http, $window, $location, Redirections, AuthenticationService) ->
$scope.path = "/signup"
$scope.spree_user.password_confirmation = ''
$scope.errors =
email: null
password: null
$scope.submit = ->
$http.post("/user/spree_user", {spree_user: $scope.spree_user, return_url: $location.absUrl()}).then (response)->
$scope.errors = {email: null, password: null}
$scope.messages = t('devise.user_registrations.spree_user.signed_up_but_unconfirmed')
if window._paq
window._paq.push(['trackEvent', 'Signin/Signup', 'Signup Submit Success', $location.absUrl()]);
.catch (response) ->
$scope.errors = response.data

View File

@@ -1,12 +0,0 @@
angular.module('Darkswarm').controller "AuthenticationCtrl", ($scope, AuthenticationService, SpreeUser)->
$scope.open = AuthenticationService.open
$scope.toggle = AuthenticationService.toggle
$scope.spree_user = SpreeUser.spree_user
$scope.isActive = AuthenticationService.isActive
$scope.select = AuthenticationService.select
$scope.tabs =
login: { active: $scope.isActive('/login') }
signup: { active: $scope.isActive('/signup') }
forgot: { active: $scope.isActive('/forgot') }

View File

@@ -1,4 +1,4 @@
angular.module('Darkswarm').controller "CheckoutCtrl", ($scope, localStorageService, Checkout, CurrentUser, CurrentHub, AuthenticationService, SpreeUser, $http) ->
angular.module('Darkswarm').controller "CheckoutCtrl", ($scope, localStorageService, Checkout, CurrentUser, CurrentHub, $http) ->
$scope.Checkout = Checkout
$scope.submitted = false
@@ -35,14 +35,9 @@ angular.module('Darkswarm').controller "CheckoutCtrl", ($scope, localStorageServ
$scope.$broadcast 'purchaseFormInvalid', $scope.formdata
$scope.ensureUserIsGuest = (callback = null) ->
$http.post("/user/registered_email", {email: $scope.order.email}).then (response)->
if response.data.registered == true
$scope.promptLogin()
else
$http.post("/user/registered_email", {email: $scope.order.email})
.then (response)->
window.CableReady.perform(response.data)
.catch ->
$scope.validateForm() if $scope.submitted
callback() if callback
$scope.promptLogin = ->
SpreeUser.spree_user.email = $scope.order.email
AuthenticationService.pushMessage t('devise.failure.already_registered')
AuthenticationService.open '/login'

View File

@@ -1,4 +1,4 @@
angular.module('Darkswarm').controller "DetailsCtrl", ($scope, $timeout, $http, CurrentUser, AuthenticationService, SpreeUser, $controller) ->
angular.module('Darkswarm').controller "DetailsCtrl", ($scope, $timeout, $http, CurrentUser, SpreeUser, $controller) ->
angular.extend this, $controller('FieldsetMixin', {$scope: $scope})
$scope.name = "details"

View File

@@ -1,11 +0,0 @@
angular.module('Darkswarm').directive 'auth', (AuthenticationService) ->
restrict: 'A'
link: (scope, elem, attrs) ->
elem.bind "click", ->
AuthenticationService.open '/' + attrs.auth
window.addEventListener "login:modal:open", ->
AuthenticationService.open '/login'
scope.$on "$destroy", ->
window.removeEventListener "login:modal:open"

View File

@@ -1,57 +0,0 @@
# This class deals with displaying things in the login modal. It chooses
# the modal tab templates and deals with switching tabs and passing data
# between the tabs. It has direct access to the instance of the login modal,
# and provides that access to other controllers as a service.
angular.module('Darkswarm').factory "AuthenticationService", (Navigation, $modal, $location, Redirections, Loading)->
new class AuthenticationService
selectedPath: "/login"
modalMessage: null
constructor: ->
if $location.path() in ["/login", "/signup", "/forgot"] || location.pathname is '/register/auth'
@open @initialTab(), @initialTemplate()
open: (path = false, template = 'authentication.html') =>
@modalInstance = $modal.open
templateUrl: template
windowClass: "login-modal medium"
@modalInstance.result.then @close, @close
@selectedPath = path || @selectedPath
Navigation.navigate @selectedPath
if window._paq
window._paq.push(['trackEvent', 'Signin/Signup', 'Login Modal View', window.location.href])
# Opens the /login tab if returning from email confirmation,
# the /signup tab if opened from the enterprise registration page,
# otherwise opens whichever tab is selected in the URL params ('/login', '/signup', or '/forgot')
initialTab: ->
if angular.isDefined($location.search()['validation'])
'/login'
else if location.pathname is '/register/auth'
'/signup'
else
$location.path()
# Loads the registration page modal when needed, otherwise the default modal
initialTemplate: ->
if location.pathname is '/register/auth'
'registration_authentication.html'
else
'authentication.html'
pushMessage: (message) ->
@modalMessage = String(message)
select: (path)=>
@selectedPath = path
Navigation.navigate @selectedPath
isActive: Navigation.isActive
close: ->
if location.pathname in ["/register", "/register/auth"]
Loading.message = t 'going_back_to_home_page'
location.hash = ""
location.pathname = "/"

View File

@@ -1,3 +0,0 @@
angular.module('Darkswarm').factory "Redirections", ($location)->
new class Redirections
after_login: $location.search().after_login

View File

@@ -1,7 +0,0 @@
%div{"ng-controller" => "AuthenticationCtrl"}
%tabset
%ng-include{src: "'login.html'"}
%ng-include{src: "'signup.html'"}
%ng-include{src: "'forgot.html'"}
%a.close-reveal-modal{"ng-click" => "$close()"}
%i.ofn-i_009-close

View File

@@ -1,31 +0,0 @@
%tab#forgot{ heading: "{{'forgot_password' | t}}", active: "tabs.forgot.active", select: "select(path)"}
%form{ ng: { controller: "ForgotCtrl", submit: "submit()" } }
.row
.large-12.columns
.alert-box.success{"ng-show" => "sent"}
{{ 'password_reset_sent' | t }}
.alert-box.success{"ng-show" => "messages != null"}
{{ messages }}
.alert-box.alert{"ng-show" => "errors != null"}
{{ errors }}
%a{ng: {show: 'user_unconfirmed', click: 'resend_confirmation()'}}
= t('devise.confirmations.resend_confirmation_email')
.row
.large-12.columns
%label{for: "email"} {{'signup_email' | t}}
%input.title.input-text{name: "email",
type: "email",
id: "email",
tabindex: 1,
inputmode: "email",
"ng-model" => "spree_user.email"}
.row
.large-12.columns
%input.button.primary{name: "commit",
tabindex: "3",
type: "submit",
value: "{{'reset_password' | t}}"}

View File

@@ -1,44 +0,0 @@
%tab#login-content{ heading: "{{'label_login' | t}}", active: "tabs.login.active", select: "select(path)"}
%form{ ng: { controller: "LoginCtrl", submit: "submit()" } }
.row
.large-12.columns
.alert-box.alert{"ng-show" => "errors != null"}
{{ errors }}
%a{ng: {show: 'user_unconfirmed', click: 'resend_confirmation()'}}
= t('devise.confirmations.resend_confirmation_email')
.alert-box.success{ng: {show: 'messages != null'}}
{{ messages }}
.row
.large-12.columns
%label{for: "email"} {{'email' | t}}
%input.title.input-text{name: "email",
type: "email",
id: "email",
tabindex: "1",
inputmode: "email",
"ng-model" => "spree_user.email"}
.row
.large-12.columns
%label{for: "password"} {{'password' | t}}
%input.title.input-text{name: "password",
type: "password",
id: "password",
autocomplete: "off",
tabindex: "2",
inputmode: "password",
"ng-model" => "spree_user.password"}
.row
.large-12.columns
%input{name: "remember_me",
type: "checkbox",
id: "remember_me",
value: "1",
tabindex: "3",
"ng-model" => "spree_user.remember_me"}
%label{for: "remember_me"} {{'remember_me' | t}}
.row
.large-12.columns
%input.button.primary{name: "commit",
tabindex: "4",
type: "submit",
value: "{{'label_login' | t}}"}

View File

@@ -1,17 +0,0 @@
.container
.row.modal-centered
%h2 {{'js.registration.welcome_to_ofn' | t}}
%h5 {{'js.registration.signup_or_login' | t}}:
%div{"ng-controller" => "AuthenticationCtrl"}
%tabset
%ng-include{src: "'signup.html'"}
%ng-include{src: "'login.html'"}
%ng-include{src: "'forgot.html'"}
%div{ ng: { show: "active('/signup')"} }
%hr
{{'js.registration.have_an_account' | t}}
%a{ href: "", ng: { click: "select('/login')"}}
{{'js.registration.action_login' | t}}
%a.close-reveal-modal{"ng-click" => "$close()"}
%i.ofn-i_009-close

View File

@@ -1,51 +0,0 @@
%tab#sign-up-content{ heading: "{{'label_signup' | t}}", active: 'tabs.signup.active', select: "select(path)"}
%form{ ng: { controller: "SignupCtrl", submit: "submit()" } }
.row
.large-12.columns
.alert-box.success{ng: {show: 'messages != null'}}
{{ messages }}
.large-12.columns
.alert-box.alert{ng: {show: 'errors.message != null'}}
{{ errors.message }}
.row
.large-12.columns
%label{for: "email"} {{'signup_email' | t}}
%input.title.input-text{name: "email",
type: "email",
id: "email",
tabindex: 1,
inputmode: "email",
"ng-model" => "spree_user.email"}
%span.error{"ng-show" => "errors.email != null"}
{{ errors.email.join(' ') }}
.row
.large-12.columns
%label{for: "password"} {{'choose_password' | t}}
%input.title.input-text{name: "password",
type: "password",
id: "password",
autocomplete: "off",
tabindex: 2,
inputmode: "password",
"ng-model" => "spree_user.password"}
%span.error{"ng-show" => "errors.password != null"}
{{ errors.password.join(' ') }}
.row
.large-12.columns
%label{for: "password_confirmation"} {{'confirm_password' | t}}
%input.title.input-text{name: "password_confirmation",
type: "password",
id: "password_confirmation",
autocomplete: "off",
tabindex: 2,
inputmode: "password",
"ng-model" => "spree_user.password_confirmation"}
%span.error{"ng-show" => "errors.password_confirmation != null"}
{{ errors.password_confirmation.join(' ') }}
.row
.large-12.columns
%input.button.primary{name: "commit",
tabindex: "3",
type: "submit",
value: "{{'action_signup' | t}}"}

View File

@@ -38,6 +38,7 @@ class ApplicationController < ActionController::Base
include Spree::Core::ControllerHelpers::Common
before_action :set_cache_headers # prevent cart emptying via cache when using back button #1213
before_action :set_after_login_url
include RawParams
include EnterprisesHelper
@@ -61,15 +62,9 @@ class ApplicationController < ActionController::Base
end
def set_checkout_redirect
referer_path = OpenFoodNetwork::RefererParser.path(request.referer)
if referer_path
is_checkout_path_the_referer = [main_app.checkout_path].include?(referer_path)
session["spree_user_return_to"] = if is_checkout_path_the_referer
referer_path
else
main_app.root_path
end
end
return unless URI(request.referer.to_s).path == main_app.checkout_path
session["spree_user_return_to"] = main_app.checkout_path
end
def shopfront_session
@@ -102,6 +97,10 @@ class ApplicationController < ActionController::Base
private
def set_after_login_url
store_location_for(:spree_user, params[:after_login]) if params[:after_login]
end
def shopfront_redirect
session[:shopfront_redirect]
end
@@ -111,7 +110,7 @@ class ApplicationController < ActionController::Base
end
def require_distributor_chosen
unless @distributor = current_distributor
unless (@distributor = current_distributor)
redirect_to main_app.root_path
false
end

View File

@@ -47,7 +47,7 @@ module PaymentGateways
return if session[:access_token] || params[:order_token] || spree_current_user
flash[:error] = I18n.t("spree.orders.edit.login_to_view_order")
redirect_to root_path(anchor: "login?after_login=#{request.env['PATH_INFO']}")
redirect_to root_path(anchor: "login", after_login: request.original_fullpath)
end
def validate_stock

View File

@@ -9,7 +9,7 @@ class PaymentsController < BaseController
@payment = Spree::Payment.find(params[:id])
authorize! :show, @payment.order
if url = @payment.cvv_response_message
if (url = @payment.cvv_response_message)
redirect_to url
else
redirect_to order_url(@payment.order)
@@ -21,7 +21,9 @@ class PaymentsController < BaseController
def require_logged_in
return if session[:access_token] || spree_current_user
store_location_for :spree_user, request.original_fullpath
flash[:error] = I18n.t("spree.orders.edit.login_to_view_order")
redirect_to main_app.root_path(anchor: "login?after_login=#{request.env['PATH_INFO']}")
redirect_to main_app.root_path(anchor: "/login", after_login: request.original_fullpath)
end
end

View File

@@ -19,7 +19,7 @@ class RegistrationController < BaseController
def check_user
if spree_current_user.nil?
redirect_to registration_auth_path(anchor: "signup?after_login=#{request.env['PATH_INFO']}")
redirect_to registration_auth_path(anchor: "/signup", after_login: request.original_fullpath)
elsif !spree_current_user.can_own_more_enterprises?
render :limit_reached
end

View File

@@ -58,7 +58,7 @@ class SplitCheckoutController < ::BaseController
return unless selected_payment_method&.external_gateway?
return unless (redirect_url = selected_payment_method.external_payment_url(order: @order))
render operations: cable_car.redirect_to(url: URI(redirect_url))
render operations: cable_car.redirect_to(url: redirect_url)
true
end

View File

@@ -139,8 +139,10 @@ module Spree
def require_order_authentication
return if session[:access_token] || params[:order_token] || spree_current_user
store_location_for :spree_user, request.original_fullpath
flash[:error] = I18n.t("spree.orders.edit.login_to_view_order")
redirect_to main_app.root_path(anchor: "login?after_login=#{request.env['PATH_INFO']}")
redirect_to main_app.root_path(anchor: "/login", after_login: request.original_fullpath)
end
def order_to_update

View File

@@ -16,24 +16,6 @@ module Spree
include I18nHelper
before_action :set_locale
# Overridden due to bug in Devise.
# respond_with resource, :location => new_session_path(resource_name)
# is generating bad url /session/new.user
#
# overridden to:
# respond_with resource, :location => spree.login_path
#
def create
self.resource = resource_class.send_reset_password_instructions(raw_params[resource_name])
if resource.errors.empty?
set_flash_message(:notice, :send_instructions) if is_navigational_format?
respond_with resource, location: spree.login_path
else
respond_with_navigational(resource) { render :new }
end
end
# Devise::PasswordsController allows for blank passwords.
# Silly Devise::PasswordsController!
# Fixes spree/spree#2190.

View File

@@ -6,11 +6,12 @@ require "spree/core/controller_helpers/order"
module Spree
class UserSessionsController < Devise::SessionsController
helper 'spree/base'
include Spree::Core::ControllerHelpers::Auth
include Spree::Core::ControllerHelpers::Common
include Spree::Core::ControllerHelpers::Order
include CablecarResponses
helper 'spree/base'
before_action :set_checkout_redirect, only: :create
after_action :ensure_valid_locale_persisted, only: :create
@@ -19,25 +20,16 @@ module Spree
authenticate_spree_user!
if spree_user_signed_in?
respond_to do |format|
format.html {
flash[:success] = t('devise.success.logged_in_succesfully')
redirect_back_or_default(after_sign_in_path_for(spree_current_user))
}
format.js {
render json: { email: spree_current_user.login }, status: :ok
}
end
flash[:success] = t('devise.success.logged_in_succesfully')
render operations: cable_car.redirect_to(
url: return_url_or_default(after_sign_in_path_for(spree_current_user))
)
else
respond_to do |format|
format.html {
flash.now[:error] = t('devise.failure.invalid')
render :new
}
format.js {
render json: { message: t('devise.failure.invalid') }, status: :unauthorized
}
end
render status: :unauthorized, operations: cable_car.inner_html(
"#login-feedback",
partial("layouts/alert", locals: { type: "alert", message: t('devise.failure.invalid') })
)
end
end
@@ -57,11 +49,6 @@ module Spree
Spree.t(:login)
end
def redirect_back_or_default(default)
redirect_to(session["spree_user_return_to"] || default)
session["spree_user_return_to"] = nil
end
def ensure_valid_locale_persisted
# When creating a new user session we have to wait until after a successful
# login to be able to persist a selected locale on the current user

View File

@@ -4,6 +4,7 @@ module Spree
class UsersController < ::BaseController
include Spree::Core::ControllerHelpers
include I18nHelper
include CablecarResponses
layout 'darkswarm'
@@ -26,16 +27,33 @@ module Spree
# Endpoint for queries to check if a user is already registered
def registered_email
user = Spree::User.find_by email: params[:email]
render json: { registered: user.present? }
registered = Spree::User.find_by(email: params[:email]).present?
if registered
render status: :ok, operations: cable_car.
inner_html(
"#login-feedback",
partial("layouts/alert", locals: { type: "alert", message: t('devise.failure.already_registered') })
).
dispatch_event(name: "login:modal:open")
else
head :not_found
end
end
def create
@user = Spree::User.new(user_params)
if @user.save
redirect_back_or_default(main_app.root_url)
render operations: cable_car.inner_html(
"#signup-feedback",
partial("layouts/alert", locals: { type: "success", message: t('devise.user_registrations.spree_user.signed_up_but_unconfirmed') })
)
else
render :new
render status: :unprocessable_entity, operations: cable_car.morph(
"#signup-tab",
partial("layouts/signup_tab", locals: { signup_form_user: @user })
)
end
end

View File

@@ -3,6 +3,7 @@
class UserConfirmationsController < DeviseController
# Needed for access to current_ability, so we can authorize! actions
include Spree::Core::ControllerHelpers::Auth
include CablecarResponses
# GET /resource/confirmation/new
def new
@@ -20,6 +21,12 @@ class UserConfirmationsController < DeviseController
else
set_flash_message(:error, :confirmation_not_sent)
end
else
render operations: cable_car.inner_html(
"#forgot-feedback",
partial("layouts/alert", locals: { type: "success", message: t("devise.confirmations.send_instructions") })
)
return
end
respond_with_navigational(resource){ redirect_to login_path }
@@ -39,22 +46,25 @@ class UserConfirmationsController < DeviseController
end
def after_confirmation_path_for(resource)
result =
if resource.errors.empty?
'confirmed'
else
'not_confirmed'
end
result = resource.errors.empty? ? "confirmed" : "not_confirmed"
if result == 'confirmed' && resource.reset_password_token.present?
raw_reset_password_token = resource.regenerate_reset_password_token
return spree.edit_spree_user_password_path(
reset_password_token: raw_reset_password_token
reset_password_token: resource.regenerate_reset_password_token
)
end
path = (session[:confirmation_return_url] || login_path).to_s
path += path.include?('?') ? '&' : '?'
path + "validation=#{result}"
path = session[:confirmation_return_url] || root_path(anchor: "/login")
append_query_to_url(path, "validation", result)
end
private
def append_query_to_url(url, key, value)
uri = URI.parse(url.to_s)
query = URI.decode_www_form(uri.query || "") << [key, value]
uri.query = URI.encode_www_form(query)
uri.to_s
end
end

View File

@@ -1,38 +1,36 @@
# frozen_string_literal: true
class UserPasswordsController < Spree::UserPasswordsController
include CablecarResponses
layout 'darkswarm'
before_action :set_admin_redirect, only: :edit
def create
render_unconfirmed_response && return if user_unconfirmed?
return render_unconfirmed_response if user_unconfirmed?
self.resource = resource_class.send_reset_password_instructions(raw_params[resource_name])
if resource.errors.empty?
set_flash_message(:success, :send_instructions) if is_navigational_format?
respond_with resource, location: main_app.login_path
render operations: cable_car.inner_html(
"#forgot-feedback",
partial("layouts/alert", locals: { type: "success", message: t(:password_reset_sent) })
)
else
respond_to do |format|
format.html do
respond_with_navigational(resource) { render :new }
end
format.js do
render json: { error: t('email_not_found') }, status: :not_found
end
end
render status: :not_found, operations: cable_car.inner_html(
"#forgot-feedback",
partial("layouts/alert", locals: { type: "alert", message: t(:email_not_found) })
)
end
end
private
def set_admin_redirect
session["spree_user_return_to"] = params[:return_to] if params[:return_to]
end
def render_unconfirmed_response
render json: { error: t('email_unconfirmed') }, status: :unauthorized
render status: :unprocessable_entity, operations: cable_car.inner_html(
"#forgot-feedback",
partial("layouts/alert",
locals: { type: "alert", message: t(:email_unconfirmed), unconfirmed: true })
)
end
def user_unconfirmed?

View File

@@ -3,17 +3,17 @@
.small-12.columns.text-center
%h3.pad-top
= t :checkout_headline
.row.pad-top
-if guest_checkout_allowed?
.row.pad-top{ "data-controller": "login-modal" }
- if guest_checkout_allowed?
.small-5.columns.text-center
%button.primary.expand{"auth" => "login"}
%button.primary.expand{ "data-action": "click->login-modal#call" }
= t :label_login
.small-2.columns.text-center
%p.pad-top= "#{t :action_or}"
.small-5.columns.text-center
%button.neutral-btn.dark.expand{"ng-click" => "enabled = true"}
= t :checkout_as_guest
-else
- else
.small-6.columns.small-centered
%button.primary.expand{"auth" => "login"}
%button.primary.expand{ "data-action": "click->login-modal#call" }
= t :label_login

View File

@@ -0,0 +1,5 @@
.alert-box{ class: "#{type}" }
= message
- if local_assigns[:unconfirmed]
%a{ "data-action": "login-modal#resend_confirmation" }
= t('devise.confirmations.resend_confirmation_email')

View File

@@ -0,0 +1,12 @@
#forgot-tab
= form_with url: spree_user_password_path, scope: :spree_user, data: { remote: "true" } do |form|
.row
.large-12.columns#forgot-feedback
.row
.large-12.columns
= form.label :email, t(:signup_email)
= form.email_field :email, { tabindex: 1, inputmode: "email", "data-login-modal-target": "email", "data-action": "input->login-modal#emailOnInput" }
.row
.large-12.columns
= form.submit t(:reset_password), { class: "button primary", tabindex: 2 }

View File

@@ -0,0 +1,24 @@
%div{"data-controller": "login-modal" }
.reveal-modal-bg.fade{ "data-login-modal-target": "background", "data-action": "click->login-modal#close" }
.reveal-modal.fade.login-modal.medium{ "data-login-modal-target": "modal" }
%div{ "data-controller": "tabs" }
%dl.tabs
%dd
%a{ data: { "tabs-target": "tab", "action": "tabs#select" } }= t(:label_login)
%dd
%a{ data: { "tabs-target": "tab", "action": "tabs#select" } }= t(:label_signup)
%dd
%a{ data: { "tabs-target": "tab", "action": "tabs#select" } }= t(:forgot_password)
.tabs-content
.content.active
%div{ data: { "tabs-target": "content" } }
= render "layouts/login_tab"
%div{ data: { "tabs-target": "content" } }
= render "layouts/signup_tab"
%div{ data: { "tabs-target": "content" } }
= render "layouts/forgot_tab"
%a.close-reveal-modal{ "data-action": "click->login-modal#close" }
%i.ofn-i_009-close

View File

@@ -0,0 +1,24 @@
#login-content
= form_with url: spree.spree_user_session_path, scope: :spree_user, data: { remote: "true" } do |form|
.row
.large-12.columns#login-feedback
- confirmation_result = request.query_parameters[:validation]
- if confirmation_result.in? ["confirmed", "not_confirmed"]
.alert-box{ class: "#{confirmation_result == "confirmed" ? "success" : "alert" }" }
= t("devise.confirmations.#{confirmation_result}")
.row
.large-12.columns
= form.label :email, t(:email)
= form.email_field :email, { tabindex: 1, inputmode: "email", autocomplete: "off", "data-login-modal-target": "email", "data-action": "input->login-modal#emailOnInput" }
.row
.large-12.columns
= form.label :password, t(:password)
= form.password_field :password, { tabindex: 2, inputmode: "password" }
.row
.large-12.columns
= form.check_box :remember_me, { tabindex: 3 }
= form.label :remember_me, t(:remember_me)
.row
.large-12.columns
= form.submit t(:label_login), { class: "button primary", tabindex: 4 }

View File

@@ -0,0 +1,25 @@
- signup_form_user = Spree::User.new if local_assigns[:signup_form_user].nil?
#signup-tab
= form_with model: signup_form_user, url: spree.account_path, scope: :user, data: { remote: "true" } do |form|
.row
.large-12.columns#signup-feedback
.row
.large-12.columns
= form.label :email, t(:signup_email)
= form.email_field :email, { tabindex: 1, "data-login-modal-target": "email", "data-action": "input->login-modal#emailOnInput" }
= form.error_message_on :email
.row
.large-12.columns
= form.label :password, t(:choose_password)
= form.password_field :password, { tabindex: 2, autocomplete: "off" }
= form.error_message_on :password
.row
.large-12.columns
= form.label :password_confirmation, t(:confirm_password)
= form.password_field :password_confirmation, { tabindex: 3, autocomplete: "off" }
= form.error_message_on :password_confirmation
.row
.large-12.columns
= form.submit t(:action_signup), { class: "button primary", tabindex: 4 }

View File

@@ -57,3 +57,5 @@
= yield :injection_data
= render "layouts/matomo_tag"
= render "layouts/login_modal"

View File

@@ -1 +1,28 @@
%div{"ng-controller" => "AuthenticationCtrl"}
%div{"data-controller": "login-modal" }
.reveal-modal-bg.fade.in{ "data-login-modal-target": "background", "data-action": "click->login-modal#returnHome", style: "display: block;" }
.reveal-modal.fade.login-modal.medium.in{ "data-login-modal-target": "modal", style: "display: block;" }
.row.modal-centered
%h2= t('js.registration.welcome_to_ofn')
%h5= t('js.registration.signup_or_login')
%div{ "data-controller": "tabs" }
%dl.tabs
%dd
%a{ data: { "tabs-target": "tab", "action": "tabs#select" } }= t(:label_signup)
%dd
%a{ data: { "tabs-target": "tab", "action": "tabs#select" } }= t(:label_login)
%dd
%a{ data: { "tabs-target": "tab", "action": "tabs#select" } }= t(:forgot_password)
.tabs-content
.content.active
%div{ data: { "tabs-target": "content" } }
= render "layouts/signup_tab"
%div{ data: { "tabs-target": "content" } }
= render "layouts/login_tab"
%div{ data: { "tabs-target": "content" } }
= render "layouts/forgot_tab"
%a.close-reveal-modal{ "data-action": "click->login-modal#returnHome" }
%i.ofn-i_009-close

View File

@@ -1,12 +0,0 @@
- if spree_current_user.nil?
%li#login-link= link_to t(:label_login), "#login", id: "sidebarLoginButton", class: "sidebar-button"
%li#login-name.hide
%li.divider
%li#sign-up-link= link_to t(:label_signup), "#signup", id: "sidebarSignUpButton", class: "sidebar-button"
%li#sign-out-link.hide= link_to "Sign Out", "/logout"
- else
%li#login-link.hide= link_to t(:label_login), "#sidebar", id: "sidebarLoginButton", class: "sidebar-button"
%li#login-name= link_to "#{spree_current_user.email}", "#"
%li.divider
%li#sign-up-link.hide= link_to t(:label_signup), "#"
%li#sign-out-link= link_to t(:label_logout), "/logout"

View File

@@ -1,5 +1,5 @@
%li#login-link
%a{"auth" => "login"}
%li#login-link{ "data-controller": "login-modal" }
%a{"auth": "login", "data-action": "click->login-modal#call" }
%img{ src: image_pack_path("menu/icn-login.svg") }
%span
= t 'label_login'

View File

@@ -0,0 +1,79 @@
import { Controller } from "stimulus"
import CableReady from "cable_ready"
export default class extends Controller {
static targets = ["background", "modal", "email"]
static values = { email: String }
connect() {
if(this.hasModalTarget) {
window.addEventListener("login:modal:open", this.open)
if(location.hash.substr(1).includes("/login")) {
this.open()
}
}
}
call(event) {
event.preventDefault()
window.dispatchEvent(new Event("login:modal:open"))
}
emailOnInput(event) {
this.emailValue = event.currentTarget.value
this.emailTargets.forEach((element) => {
element.value = this.emailValue
})
}
open = () => {
if(!location.hash.substr(1).includes("/login")) {
history.pushState({}, "", "#/login")
}
this.backgroundTarget.style.display = "block"
this.modalTarget.style.display = "block"
setTimeout(() => {
this.modalTarget.classList.add("in")
this.backgroundTarget.classList.add("in")
document.querySelector("body").classList.add("modal-open")
})
window._paq?.push(['trackEvent', 'Signin/Signup', 'Login Modal View', window.location.href])
}
close() {
history.pushState({}, "", window.location.pathname + window.location.search)
this.modalTarget.classList.remove("in")
this.backgroundTarget.classList.remove("in")
document.querySelector("body").classList.remove("modal-open")
setTimeout(() => {
this.backgroundTarget.style.display = "none"
this.modalTarget.style.display = "none"
}, 200)
}
resend_confirmation() {
fetch("/user/spree_user/confirmation", {
method: "POST",
body: JSON.stringify({
spree_user: { email: this.emailValue }
})
}).then(data => data.json()).then(CableReady.perform)
}
returnHome() {
window.location = "/"
}
disconnect() {
if(this.hasModalTarget) {
window.removeEventListener("login:modal:open", this.open)
}
}
}

View File

@@ -0,0 +1,36 @@
import { Controller } from "stimulus";
export default class extends Controller {
static targets = ["tab", "content"]
select(event) {
this.setCurrentTab(this.tabTargets.indexOf(event.currentTarget))
}
// private
connect() {
this.setCurrentTab()
}
setCurrentTab(tabIndex = 0) {
this.showSelectedContent(tabIndex)
this.setButtonActiveClass(tabIndex)
}
showSelectedContent(tabIndex) {
this.contentTargets.forEach((element, index) => {
element.hidden = index !== tabIndex
})
}
setButtonActiveClass(tabIndex) {
this.tabTargets.forEach((element, index) => {
if(index === tabIndex) {
element.classList.add("active")
} else {
element.classList.remove("active")
}
})
}
}

View File

@@ -5,6 +5,21 @@
.login-modal {
background: $modal-background-color;
visibility: visible;
position: fixed;
top: 3em;
a.active {
background-color: white;
&:hover {
background-color: white;
}
}
span.formError {
@extend .error;
}
.tabs-content {
background: $modal-content-background-color;

View File

@@ -14,6 +14,7 @@ import { CableCar } from "mrujs/plugins"
import * as Turbo from "@hotwired/turbo"
window.Turbo = Turbo
window.CableReady = CableReady
mrujs.start({
plugins: [
new CableCar(CableReady)

View File

@@ -1,5 +1,4 @@
angular.module('Darkswarm').factory "CookiesBannerService", (Navigation, $modal, $location, Redirections, Loading)->
angular.module('Darkswarm').factory "CookiesBannerService", (Navigation, $modal, $location, Loading)->
new class CookiesBannerService
modalMessage: null
isEnabled: false

View File

@@ -29,7 +29,8 @@ module Spree
redirect_to '/unauthorized'
else
store_location
redirect_to main_app.root_path(anchor: "login?after_login=#{request.env['PATH_INFO']}")
redirect_to main_app.root_path(anchor: "/login", after_login: request.original_fullpath)
end
end
@@ -49,9 +50,8 @@ module Spree
session['spree_user_return_to'] = request.fullpath.gsub('//', '/')
end
def redirect_back_or_default(default)
redirect_to(session["spree_user_return_to"] || default)
session["spree_user_return_to"] = nil
def return_url_or_default(default)
session.delete("spree_user_return_to") || default
end
# Need to generate an API key for a user due to some actions potentially

View File

@@ -6,7 +6,7 @@ describe RegistrationController, type: :controller do
describe "redirecting when user not logged in" do
it "index" do
get :index
expect(response).to redirect_to registration_auth_path(anchor: "signup?after_login=/register")
expect(response).to redirect_to registration_auth_path(anchor: "/signup", after_login: "/register")
end
end

View File

@@ -12,7 +12,7 @@ describe Spree::Admin::BaseController, type: :controller do
it "redirects to Angular login" do
spree_get :index
expect(response).to redirect_to root_path(anchor: "login?after_login=/spree/admin/base")
expect(response).to redirect_to root_path(anchor: "/login", after_login: "/spree/admin/base")
end
describe "rendering as json ActiveModelSerializer" do

View File

@@ -77,7 +77,7 @@ describe Spree::OrdersController, type: :controller do
it "redirects to unauthorized" do
get :show, params: { id: order.number }
expect(response).to redirect_to(root_path(anchor: "login?after_login=#{order_path(order)}"))
expect(response).to redirect_to(root_path(anchor: "/login", after_login: order_path(order)))
expect(flash[:error]).to eq("Please log in to view your order.")
end
end

View File

@@ -10,12 +10,13 @@ describe Spree::UserSessionsController, type: :controller do
end
describe "create" do
context "succeed" do
context "success" do
context "when referer is not '/checkout'" do
it "redirects to root" do
spree_post :create, spree_user: { email: user.email, password: user.password },
use_route: :spree
expect(response).to redirect_to root_path
spree_post :create, spree_user: { email: user.email, password: user.password }
expect(response).to have_http_status(:ok)
expect(response.body).to match(root_path).and match("redirect")
end
end
@@ -23,12 +24,24 @@ describe Spree::UserSessionsController, type: :controller do
before { @request.env['HTTP_REFERER'] = 'http://test.com/checkout' }
it "redirects to checkout" do
spree_post :create, spree_user: { email: user.email, password: user.password },
use_route: :spree
expect(response).to redirect_to checkout_path
spree_post :create, spree_user: { email: user.email, password: user.password }
expect(response).to have_http_status(:ok)
expect(response.body).to match(checkout_path).and match("redirect")
end
end
end
context "failing to log in" do
render_views
it "returns an error" do
spree_post :create, spree_user: { email: user.email, password: "wrong" }
expect(response).to have_http_status(:unauthorized)
expect(response.body).to include "Invalid email or password"
end
end
end
describe "destroy" do

View File

@@ -60,14 +60,14 @@ describe Spree::UsersController, type: :controller do
let!(:user) { create(:user) }
it "returns true if email corresponds to a registered user" do
it "returns ok (200) if email corresponds to a registered user" do
post :registered_email, params: { email: user.email }
expect(json_response['registered']).to eq true
expect(response).to have_http_status(:ok)
end
it "returns false if email does not correspond to a registered user" do
it "returns not_found (404) if email does not correspond to a registered user" do
post :registered_email, params: { email: 'nonregistereduser@example.com' }
expect(json_response['registered']).to eq false
expect(response).to have_http_status(:not_found)
end
end

View File

@@ -22,7 +22,7 @@ describe UserConfirmationsController, type: :controller do
end
it "redirects the user to login" do
expect(response).to redirect_to login_path(validation: 'not_confirmed')
expect(response).to redirect_to root_path(anchor: "/login", validation: "not_confirmed")
end
end
@@ -34,19 +34,21 @@ describe UserConfirmationsController, type: :controller 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')
expect(response).to redirect_to root_path(anchor: "/login", validation: "confirmed")
end
it "redirects to previous url, if present" do
session[:confirmation_return_url] = producers_path + '#/login'
session[:confirmation_return_url] = producers_path(anchor: "#/login")
spree_get :show, confirmation_token: unconfirmed_user.confirmation_token
expect(response).to redirect_to producers_path + '#/login?validation=confirmed'
expect(response).to redirect_to producers_path(anchor: "#/login", validation: "confirmed")
end
it "redirects to previous url on /register path" do
session[:confirmation_return_url] = registration_path + '#/signup?after_login=%2Fregister'
session[:confirmation_return_url] = registration_path(anchor: "#/signup", after_login: "/register")
spree_get :show, confirmation_token: unconfirmed_user.confirmation_token
expect(response).to redirect_to registration_path + '#/signup?after_login=%2Fregister&validation=confirmed'
expect(response).
to redirect_to registration_path(anchor: "#/signup",
after_login: "/register", validation: "confirmed")
end
it "redirects to set password page, if user needs to reset their password" do

View File

@@ -3,7 +3,7 @@
require 'spec_helper'
describe UserPasswordsController, type: :controller do
include OpenFoodNetwork::EmailHelper
render_views
let(:user) { create(:user) }
let(:unconfirmed_user) { create(:user, confirmed_at: nil) }
@@ -13,30 +13,35 @@ describe UserPasswordsController, type: :controller do
end
describe "create" do
it "returns errors" do
spree_post :create, spree_user: {}
expect(response.status).to eq 200
expect(response).to render_template "spree/user_passwords/new"
it "returns 404 if user is not found" do
spree_post :create, spree_user: { email: "xxxxxxxxxx@example.com" }
expect(response.status).to eq 404
expect(response.body).to match I18n.t(:email_not_found)
end
it "redirects to login when data is valid" do
it "returns 422 if user is registered but not confirmed" do
spree_post :create, spree_user: { email: unconfirmed_user.email }
expect(response.status).to eq 422
expect(response.body).to match I18n.t(:email_unconfirmed)
end
it "returns 200 when password reset was successful" do
spree_post :create, spree_user: { email: user.email }
expect(response).to be_redirect
expect(response.status).to eq 200
expect(response.body).to match I18n.t(:password_reset_sent)
end
end
describe "edit" do
context "when given a redirect" do
it "stores the redirect path in 'spree_user_return_to'" do
spree_post :edit, reset_password_token: "token", return_to: "/return_path"
spree_post :edit, reset_password_token: "token", after_login: "/return_path"
expect(session["spree_user_return_to"]).to eq "/return_path"
end
end
end
it "renders Darkswarm" do
setup_email
user.send_reset_password_instructions
user.reload
@@ -44,19 +49,4 @@ describe UserPasswordsController, type: :controller do
expect(response).to render_template "user_passwords/edit"
end
describe "via ajax" do
it "returns error when email not found" do
post :create, xhr: true, params: { spree_user: {}, use_route: :spree }
expect(response.status).to eq 404
expect(json_response).to eq 'error' => I18n.t('email_not_found')
end
it "returns error when user is unconfirmed" do
post :create, xhr: true,
params: { spree_user: { email: unconfirmed_user.email }, use_route: :spree }
expect(response.status).to eq 401
expect(json_response).to eq 'error' => I18n.t('email_unconfirmed')
end
end
end

View File

@@ -0,0 +1,67 @@
/**
* @jest-environment jsdom
*/
import { Application } from "stimulus";
import tabs_controller from "../../../app/webpacker/controllers/tabs_controller";
describe("TabsController", () => {
describe("#select", () => {
beforeEach(() => {
document.body.innerHTML = `
<div data-controller="tabs">
<button data-tabs-target="tab" data-action="click->tabs#select">Dogs</button>
<button data-tabs-target="tab" data-action="click->tabs#select">Cats</button>
<button data-tabs-target="tab" data-action="click->tabs#select">Birds</button>
<div class="content-area" data-tabs-target="content" >
Dogs content
</div>
<div class="content-area" data-tabs-target="content" >
Cats content
</div>
<div class="content-area" data-tabs-target="content" >
Birds content
</div>
</div>
`;
const application = Application.start();
application.register("tabs", tabs_controller);
});
it("shows the corresponding content when a tab button is clicked", () => {
const dogs_button = document.querySelectorAll('button')[0];
const cats_button = document.querySelectorAll('button')[1];
const birds_button = document.querySelectorAll('button')[2];
const dogs_content = document.querySelectorAll('.content-area')[0];
const cats_content = document.querySelectorAll('.content-area')[1];
const birds_content = document.querySelectorAll('.content-area')[2];
expect(dogs_content.hidden).toBe(false);
expect(cats_content.hidden).toBe(true);
expect(birds_content.hidden).toBe(true);
expect(document.querySelectorAll('button.active').length).toBe(1);
expect(document.querySelectorAll('button.active')[0]).toBe(dogs_button);
birds_button.click();
expect(dogs_content.hidden).toBe(true);
expect(cats_content.hidden).toBe(true);
expect(birds_content.hidden).toBe(false);
expect(document.querySelectorAll('button.active').length).toBe(1);
expect(document.querySelectorAll('button.active')[0]).toBe(birds_button);
cats_button.click();
expect(dogs_content.hidden).toBe(true);
expect(cats_content.hidden).toBe(false);
expect(birds_content.hidden).toBe(true);
expect(document.querySelectorAll('button.active').length).toBe(1);
expect(document.querySelectorAll('button.active')[0]).toBe(cats_button);
});
});
});

View File

@@ -24,8 +24,8 @@ module AuthenticationHelper
end
def fill_in_and_submit_login_form(user)
fill_in "email", with: user.email
fill_in "password", with: user.password
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Login"
end

View File

@@ -111,8 +111,8 @@ module UIComponentHelper
end
def fill_in_using_keyboard
page.find('#email').send_keys(user.email, :tab, user.password, :tab, :space)
expect(page.find('#remember_me')).to be_checked
page.find('#remember_me').send_keys(:tab, :enter)
page.find('#spree_user_email').send_keys(user.email, :tab, user.password, :tab, :space)
expect(page.find('#spree_user_remember_me')).to be_checked
page.find('#spree_user_remember_me').send_keys(:tab, :enter)
end
end

View File

@@ -12,7 +12,7 @@ describe "Authentication", js: true do
describe "With redirects" do
it "logging in with a redirect set" do
visit groups_path(anchor: "login?after_login=#{producers_path}")
visit groups_path(anchor: "/login", after_login: producers_path)
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_login_button
@@ -61,38 +61,35 @@ describe "Authentication", js: true do
end
it "Failing to sign up because password is too short" do
fill_in "Email", with: "test@foo.com"
fill_in "Your email", with: "test@foo.com"
fill_in "Choose a password", with: "short"
click_signup_button
expect(page).to have_content "too short"
end
it "Failing to sign up because email is already registered" do
fill_in "Email", with: user.email
fill_in "Your email", with: user.email
fill_in "Choose a password", with: "foobarino"
click_signup_button
expect(page).to have_content "There's already an account for this email."
end
it "Failing to sign up because password confirmation doesn't match or is blank" do
fill_in "Email", with: user.email
fill_in "Your email", with: user.email
fill_in "Choose a password", with: "ForgotToRetype"
click_signup_button
expect(page).to have_content "doesn't match"
end
it "Signing up successfully" do
performing_deliveries do
setup_email
fill_in "Email", with: "test@foo.com"
fill_in "Choose a password", with: "test12345"
fill_in "Confirm password", with: "test12345"
fill_in "Your email", with: "test@foo.com"
fill_in "Choose a password", with: "test12345"
fill_in "Confirm password", with: "test12345"
expect do
click_signup_button
expect(page).to have_content I18n.t('devise.user_registrations.spree_user.signed_up_but_unconfirmed')
end.to enqueue_job ActionMailer::MailDeliveryJob
end
expect do
click_signup_button
expect(page).to have_content I18n.t('devise.user_registrations.spree_user.signed_up_but_unconfirmed')
end.to enqueue_job ActionMailer::MailDeliveryJob
end
end
@@ -161,7 +158,7 @@ describe "Authentication", js: true do
describe "after following email confirmation link" do
it "shows confirmed message in modal" do
visit '/#/login?validation=confirmed'
visit root_path(anchor: "/login", validation: "confirmed")
expect(page).to have_login_modal
expect(page).to have_content I18n.t('devise.confirmations.confirmed')
end

View File

@@ -29,6 +29,20 @@ describe "User password confirm/reset page" do
expect(page).to have_no_text "Reset password token has expired"
expect(page).to be_logged_in_as user
end
it "shows an error if password is empty" do
visit spree.spree_user_confirmation_path(confirmation_token: user.confirmation_token)
expect(user.reload.confirmed?).to be true
expect(page).to have_text I18n.t(:change_my_password)
fill_in "Password", with: ""
fill_in "Password Confirmation", with: ""
click_button
expect(page).to have_text "User password cannot be blank. Please enter a password."
expect(page).to_not be_logged_in_as user
end
end
describe "can reset its own password" do