Merge branch 'master' into remove-inline-advanced-settings-toggle-js

This commit is contained in:
Cillian O'Ruanaidh
2022-02-04 12:24:17 +00:00
96 changed files with 1179 additions and 899 deletions

View File

@@ -1,7 +1,11 @@
$(document).ready ->
progressTimer = null
$(document).ajaxStart ->
$("#progress").fadeIn()
progressTimer = setTimeout ->
$("#progress").fadeIn()
, 500
$(document).ajaxStop ->
$("#progress").fadeOut()
clearTimeout(progressTimer) if progressTimer?
$("#progress").stop().hide()

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,54 +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
# 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

@@ -6,6 +6,6 @@
.sixteen.columns.alpha#loading{ 'ng-show' => 'productsLoading()' }
%br
%img.spinner{ src: image_path("/spinning-circles.svg")}
%i.spinner.fa.fa-spin.fa-circle-o-notch
%h1
{{ 'js.admin.panels.exchange_products.loading_variants' | t }}

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,48 +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}}"}
:javascript
if (window._paq) {
window._paq.push(['trackEvent', 'Signin/Signup', 'Login Modal View', window.location.href]);
}

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
@@ -139,17 +138,6 @@ class ApplicationController < ActionController::Base
!current_distributor.ready_for_checkout?
end
def check_order_cycle_expiry
if current_order_cycle&.closed?
Bugsnag.notify("Notice: order cycle closed during checkout completion", order: current_order)
current_order.empty!
current_order.set_order_cycle! nil
flash[:info] = I18n.t('order_cycle_closed')
redirect_to main_app.shop_path
end
end
# All render calls within the block will be performed with the specified format
# Useful for rendering html within a JSON response, particularly if the specified
# template or partial then goes on to render further partials without specifying

View File

@@ -12,7 +12,6 @@ class BaseController < ApplicationController
include OrderCyclesHelper
before_action :set_locale
before_action :check_order_cycle_expiry
private

View File

@@ -14,7 +14,7 @@ module CheckoutCallbacks
prepend_before_action :require_distributor_chosen
before_action :load_order, :associate_user, :load_saved_addresses, :load_saved_credit_cards
before_action :load_shipping_methods, :load_countries, if: -> { params[:step] == "details" }
before_action :load_shipping_methods, if: -> { params[:step] == "details" }
before_action :ensure_order_not_completed
before_action :ensure_checkout_allowed
@@ -49,15 +49,6 @@ module CheckoutCallbacks
@shipping_methods = Spree::ShippingMethod.for_distributor(@order.distributor).order(:name)
end
def load_countries
@countries = available_countries.map { |c| [c.name, c.id] }
@countries_with_states = available_countries.map { |c|
[c.id, c.states.map { |s|
[s.name, s.id]
}]
}
end
def redirect_to_shop?
!@order ||
!@order.checkout_allowed? ||

View File

@@ -18,6 +18,17 @@ module OrderStockCheck
redirect_to main_app.cart_path
end
def check_order_cycle_expiry
return unless current_order_cycle&.closed?
Bugsnag.notify("Notice: order cycle closed during checkout completion", order: current_order)
current_order.empty!
current_order.set_order_cycle! nil
flash[:info] = I18n.t('order_cycle_closed')
redirect_to main_app.shop_path
end
private
def sufficient_stock?

View File

@@ -8,6 +8,7 @@ module PaymentGateways
before_action :destroy_orphaned_paypal_payments, only: :confirm
before_action :load_checkout_order, only: [:express, :confirm]
before_action :handle_insufficient_stock, only: [:express, :confirm]
before_action :check_order_cycle_expiry, only: [:express, :confirm]
before_action :permit_parameters!
after_action :reset_order_when_complete, only: :confirm

View File

@@ -7,6 +7,7 @@ module PaymentGateways
before_action :load_checkout_order, only: :confirm
before_action :validate_payment_intent, only: :confirm
before_action :check_order_cycle_expiry, only: :confirm
before_action :validate_stock, only: :confirm
def confirm
@@ -46,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
@@ -71,6 +71,7 @@ class SplitCheckoutController < ::BaseController
@order.select_shipping_method(params[:shipping_method_id])
@order.update(order_params)
@order.updater.update_totals_and_states
validate_current_step!

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

@@ -17,6 +17,24 @@ module Spree
end.sort { |a, b| a.name <=> b.name }
end
def countries
available_countries.map { |c| [c.name, c.id] }
end
def states_for_country(country)
country.states.map do |state|
[state.name, state.id]
end
end
def countries_with_states
available_countries.map { |c|
[c.id, c.states.map { |s|
[s.name, s.id]
}]
}
end
def pretty_time(time)
[I18n.l(time.to_date, format: :long),
time.strftime("%l:%M %p")].join(" ")

View File

@@ -79,6 +79,8 @@ module Spree
after_create :create_stock_items
after_create :set_position
before_save :convert_variant_weight_to_decimal
around_destroy :destruction
# default variant scope only lists non-deleted variants
@@ -243,5 +245,9 @@ module Spree
self.unit_value = 1.0
end
def convert_variant_weight_to_decimal
self.weight = weight.to_d
end
end
end

View File

@@ -35,9 +35,9 @@
.row{ 'ng-if' => 'shop_id && RequestMonitor.loading' }
.sixteen.columns.alpha#loading
= render partial: "components/spinner"
= render partial: "components/admin_spinner"
%h1
=t :loading_customers
= t :loading_customers
.row{ :class => "sixteen columns alpha", 'ng-show' => '!RequestMonitor.loading && filteredCustomers.length == 0'}
%h1#no_results

View File

@@ -9,8 +9,9 @@
%columns-dropdown{ action: "#{controller_name}_#{action_name}" }
.row{ 'ng-if' => '!loaded' }
.sixteen.columns.alpha#loading
= render partial: "components/spinner"
%h1= t('.loading_enterprises')
= render partial: "components/admin_spinner"
%h1
= t('.loading_enterprises')
.row{ :class => "sixteen columns alpha", 'ng-show' => 'loaded && filteredEnterprises.length == 0'}
%h1#no_results= t('.no_enterprises_found')

View File

@@ -57,7 +57,7 @@
= f.text_field :permalink, { 'ng-model' => "Enterprise.permalink", placeholder: "eg. your-shop-name", 'ng-model-options' => "{ updateOn: 'default blur', debounce: {'default': 300, 'blur': 0} }" }
.two.columns.omega
%div{ng: {show: "checking", cloak: true}, style: "width: 30px; height: 30px;"}
= render partial: "components/spinner"
= render partial: "components/admin_spinner"
%span{ ng: { class: 'availability.toLowerCase()', hide: "checking" } }
{{ availability }}
%i{ ng: { class: "{'icon-ok-sign': availability == 'Available', 'icon-remove-sign': availability == 'Unavailable'}" } }

View File

@@ -1,6 +1,6 @@
%div.sixteen.columns.alpha.omega#loading{ ng: { cloak: true, if: 'RequestMonitor.loading' } }
= render partial: "components/spinner"
= render partial: "components/admin_spinner"
%h1{ ng: { hide: 'orderCycles.length > 0' } }
=t('.loading_order_cycles')
= t('.loading_order_cycles')
%h1{ ng: { show: 'orderCycles.length > 0' } }
=t('.loading')
= t('.loading')

View File

@@ -1,3 +1,4 @@
%div.sixteen.columns.alpha.omega#loading{ ng: { cloak: true, if: 'shop_id && RequestMonitor.loading' } }
= render partial: "components/spinner"
%h1= t('.loading')
= render partial: "components/admin_spinner"
%h1
= t('.loading')

View File

@@ -1,3 +1,4 @@
%div.sixteen.columns.alpha.omega#loading{ ng: { cloak: true, if: 'hub_id && products.length == 0 && RequestMonitor.loading' } }
= render partial: "components/spinner"
%h1= t('.loading_inventory')
= render partial: "components/admin_spinner"
%h1
= t('.loading_inventory')

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 @@
%i.spinner.fa.fa-spin.fa-circle-o-notch

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

@@ -25,7 +25,7 @@
= bill_address.text_field :phone, { placeholder: t("split_checkout.step1.your_details.phone.placeholder") }
= f.error_message_on "bill_address.phone"
%div.checkout-substep{ "data-controller": "dependant-select", "data-dependant-select-options-value": @countries_with_states }
%div.checkout-substep
-# BILLING ADDRESS
%div.checkout-title
= t("split_checkout.step1.billing_address.title")
@@ -45,18 +45,21 @@
= bill_address.text_field :city, { placeholder: t("split_checkout.step1.address.city.placeholder") }
= f.error_message_on "bill_address.city"
%div.checkout-input
= bill_address.label :state_id, t("split_checkout.step1.address.state_id.label")
= bill_address.select :state_id, @countries_with_states, { }, { "data-dependant-select-target": "select" }
%div.checkout-input
= bill_address.label :zipcode, t("split_checkout.step1.address.zipcode.label")
= bill_address.text_field :zipcode, { placeholder: t("split_checkout.step1.address.zipcode.placeholder") }
= f.error_message_on "bill_address.zipcode"
%div.checkout-input
= bill_address.label :country_id, t("split_checkout.step1.address.country_id.label")
= bill_address.select :country_id, @countries, { selected: @order.bill_address.country_id || DefaultCountry.id }, {"data-dependant-select-target": "source", "data-action": "dependant-select#handleSelectChange"}
%div{ "data-controller": "dependant-select", "data-dependant-select-options-value": countries_with_states }
- bill_address_country = @order.bill_address.country || DefaultCountry.country
%div.checkout-input
= bill_address.label :country_id, t("split_checkout.step1.address.country_id.label")
= bill_address.select :country_id, countries, { selected: bill_address_country.id }, { "data-dependant-select-target": "source", "data-action": "dependant-select#handleSelectChange" }
%div.checkout-input
= bill_address.label :state_id, t("split_checkout.step1.address.state_id.label")
= bill_address.select :state_id, states_for_country(bill_address_country), { selected: @order.bill_address&.state_id }, { "data-dependant-select-target": "select" }
- if spree_current_user||true
%div.checkout-input
@@ -121,18 +124,21 @@
= ship_address.text_field :city, { placeholder: t("split_checkout.step1.address.city.placeholder") }
= f.error_message_on "ship_address.city"
%div.checkout-input
= ship_address.label :state_id, t("split_checkout.step1.address.state_id.label")
= ship_address.select :state_id, @countries_with_states, { }, { "data-dependant-select-target": "select" }
%div.checkout-input
= ship_address.label :zipcode, t("split_checkout.step1.address.zipcode.label")
= ship_address.text_field :zipcode, { placeholder: t("split_checkout.step1.address.zipcode.placeholder") }
= f.error_message_on "ship_address.zipcode"
%div.checkout-input
= ship_address.label :country_id, t("split_checkout.step1.address.country_id.label")
= ship_address.select :country_id, @countries, { selected: @order.ship_address.country_id || DefaultCountry.id }, {"data-dependant-select-target": "source", "data-action": "dependant-select#handleSelectChange"}
%div{ "data-controller": "dependant-select", "data-dependant-select-options-value": countries_with_states }
- ship_address_country = @order.ship_address.country || DefaultCountry.country
%div.checkout-input
= ship_address.label :country_id, t("split_checkout.step1.address.country_id.label")
= ship_address.select :country_id, countries, { selected: ship_address_country.id }, { "data-dependant-select-target": "source", "data-action": "dependant-select#handleSelectChange" }
%div.checkout-input
= ship_address.label :state_id, t("split_checkout.step1.address.state_id.label")
= ship_address.select :state_id, states_for_country(ship_address_country), { selected: @order.ship_address&.state_id }, { "data-dependant-select-target": "select" }
- if spree_current_user
%div.checkout-input{ "data-toggle-target": "content", style: "display: none" }

View File

@@ -71,10 +71,11 @@
= render 'spree/orders/summary', order: @order
- if any_terms_required?(@order.distributor)
= render partial: "terms_and_conditions", locals: { f: f }
%div.checkout-submit.medium-6
= f.submit t("split_checkout.step3.submit"), name: "confirm_order", class: "button primary", disabled: @terms_and_conditions_accepted == false || @platform_tos_accepted == false
%a.button.cancel{href: main_app.cart_path}
= t("split_checkout.step3.cancel")
.checkout-step3{"data-controller": "sticky", "data-sticky-target": "container"}
- if any_terms_required?(@order.distributor)
= render partial: "terms_and_conditions", locals: { f: f }
.medium-6
.checkout-submit
= f.submit t("split_checkout.step3.submit"), name: "confirm_order", class: "button primary", disabled: @terms_and_conditions_accepted == false || @platform_tos_accepted == false
%a.button.cancel{href: main_app.cart_path}
= t("split_checkout.step3.cancel")

View File

@@ -19,7 +19,7 @@
.sub-header.show-for-medium-down
= render partial: "shopping_shared/order_cycles"
.row{ "data-controller": "guest-checkout", "data-guest-checkout-distributor-value": @order.distributor.id }
%div{ "data-controller": "guest-checkout", "data-guest-checkout-distributor-value": @order.distributor.id }
%div{ style: "display: #{spree_current_user ? 'block' : 'none'}", "data-guest-checkout-target": "checkout" }
= render partial: "checkout"

View File

@@ -103,7 +103,7 @@
%columns-dropdown{ action: "#{controller_name}_#{action_name}" }
%div.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' }
= render partial: "components/spinner"
= render partial: "components/admin_spinner"
%h1
= t("admin.orders.bulk_management.loading")

View File

@@ -92,13 +92,10 @@
%div{'ng-if' => 'order.ready_to_capture'}
%button.icon-capture.icon_link.no-text{'ng-click' => 'capturePayment(order)', rel: 'nofollow', 'ofn-with-tip' => t('.capture')}
.orders-loading{'ng-show' => 'RequestMonitor.loading'}
.row
.small-12.columns.fullwidth.text-center
= render partial: "components/spinner"
.row
.small-12.columns.fullwidth.text-center
%span= t('.loading')
.sixteen.columns.alpha#loading{ 'ng-show' => 'RequestMonitor.loading' }
= render partial: "components/admin_spinner"
%h1
= t('.loading')
%div{'ng-show' => "!RequestMonitor.loading && orders.length > 0" }
= render partial: 'admin/shared/angular_pagination'

View File

@@ -1,7 +1,7 @@
%div.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' }
%br
= render partial: "components/spinner"
%h1= t('.title')
= render partial: "components/admin_spinner"
%h1
= t('.title')
%div.sixteen.columns.alpha{ 'ng-show' => '!RequestMonitor.loading && products.length == 0 && q.query.length == 0' }
%h1#no_results= t('.no_products')

View File

@@ -20,10 +20,6 @@
Spree.routes.taxonomy_taxons = "#{main_app.api_v0_taxonomy_taxons_url(@taxonomy)}";
Spree.routes.admin_taxonomy_taxons = "#{spree.admin_taxonomy_taxons_url(@taxonomy)}";
#taxonomy_tree.tree
#progress{style: "display:none;"}
= image_pack_tag 'select2-spinner.gif', title: 'Spinner', style: "vertical-align:bottom;"
= t("spree.updating")
\..
.info= t("spree.taxonomy_tree_instruction")
%br/
.filter-actions.actions

View File

@@ -1,24 +1,4 @@
#progress
/ By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL
%svg{:class => "spinner", :viewbox => "0 0 58 58", :xmlns => "http://www.w3.org/2000/svg"}
%g{:fill => "none", "fill-rule" => "evenodd"}
%g{:stroke => "currentColor", "stroke-width" => "1.5", :transform => "translate(2 1)"}
%circle{:cx => "42.601", :cy => "11.462", :fill => "currentColor", "fill-opacity" => "1", :r => "5"}
%animate{:attributename => "fill-opacity", :begin => "0s", :calcmode => "linear", :dur => "1.3s", :repeatcount => "indefinite", :values => "1;0;0;0;0;0;0;0"}
%circle{:cx => "49.063", :cy => "27.063", :fill => "currentColor", "fill-opacity" => "0", :r => "5"}
%animate{:attributename => "fill-opacity", :begin => "0s", :calcmode => "linear", :dur => "1.3s", :repeatcount => "indefinite", :values => "0;1;0;0;0;0;0;0"}
%circle{:cx => "42.601", :cy => "42.663", :fill => "currentColor", "fill-opacity" => "0", :r => "5"}
%animate{:attributename => "fill-opacity", :begin => "0s", :calcmode => "linear", :dur => "1.3s", :repeatcount => "indefinite", :values => "0;0;1;0;0;0;0;0"}
%circle{:cx => "27", :cy => "49.125", :fill => "currentColor", "fill-opacity" => "0", :r => "5"}
%animate{:attributename => "fill-opacity", :begin => "0s", :calcmode => "linear", :dur => "1.3s", :repeatcount => "indefinite", :values => "0;0;0;1;0;0;0;0"}
%circle{:cx => "11.399", :cy => "42.663", :fill => "currentColor", "fill-opacity" => "0", :r => "5"}
%animate{:attributename => "fill-opacity", :begin => "0s", :calcmode => "linear", :dur => "1.3s", :repeatcount => "indefinite", :values => "0;0;0;0;1;0;0;0"}
%circle{:cx => "4.938", :cy => "27.063", :fill => "currentColor", "fill-opacity" => "0", :r => "5"}
%animate{:attributename => "fill-opacity", :begin => "0s", :calcmode => "linear", :dur => "1.3s", :repeatcount => "indefinite", :values => "0;0;0;0;0;1;0;0"}
%circle{:cx => "11.399", :cy => "11.462", :fill => "currentColor", "fill-opacity" => "0", :r => "5"}
%animate{:attributename => "fill-opacity", :begin => "0s", :calcmode => "linear", :dur => "1.3s", :repeatcount => "indefinite", :values => "0;0;0;0;0;0;1;0"}
%circle{:cx => "27", :cy => "5", :fill => "currentColor", "fill-opacity" => "0", :r => "5"}
%animate{:attributename => "fill-opacity", :begin => "0s", :calcmode => "linear", :dur => "1.3s", :repeatcount => "indefinite", :values => "0;0;0;0;0;0;0;1"}
= render partial: "components/admin_spinner"
= Spree.t(:loading)
\...

View File

@@ -4,24 +4,30 @@ export default class extends Controller {
static targets = ["source", "select"];
static values = { options: Array };
connect() {
this.populateSelect(parseInt(this.sourceTarget.value));
}
handleSelectChange() {
this.populateSelect(parseInt(this.sourceTarget.value));
}
populateSelect(sourceId) {
const allOptions = this.optionsValue;
const options = allOptions.find((option) => option[0] === sourceId)[1];
const selectBox = this.selectTarget;
selectBox.innerHTML = "";
options.forEach((item) => {
const opt = document.createElement("option");
opt.value = item[1];
opt.innerHTML = item[0];
selectBox.appendChild(opt);
this.removeCurrentOptions()
this.dependantOptionsFor(sourceId).forEach((item) => {
this.addOption(item[0], item[1])
});
}
removeCurrentOptions() {
this.selectTarget.innerHTML = ""
}
addOption(label, value) {
const newOption = document.createElement("option")
newOption.innerHTML = label
newOption.value = value
this.selectTarget.appendChild(newOption)
}
dependantOptionsFor(sourceId) {
return this.optionsValue.find((option) => option[0] === sourceId)[1]
}
}

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,24 @@
import { Controller } from "stimulus";
// This add a `sticked` class to the element (`container`) when the user scrolls down
// or up until the element is `sticked`.
// The class is then removed once the element is no more `sticked`.
// The element should have a `data-sticky-target` attribute with `container` as value.
// This is only functionnal with a sticked element at the bottom. We could improve that point
// by adding a `data-position` attribute with `top|bottom|left|right` as value and
// modify the code below to handle the different positions.
export default class extends Controller {
static targets = ["container"];
connect() {
this.containerTarget.style.position = "sticky";
this.containerTarget.style.bottom = "-1px";
const observer = new IntersectionObserver(
([e]) => {
e.target.classList.toggle("sticked", e.intersectionRatio < 1);
},
{ threshold: [1] }
);
observer.observe(this.containerTarget);
}
}

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

@@ -1,28 +1,42 @@
// Loading throbber displayed when ajax request takes too long to complete
#progress {
@include border-radius(10px);
position: fixed;
top: -10px;
left: 50%;
z-index: 1000;
opacity: 0.8;
width: 200px;
background-color: $spree-blue;
color: $color-1;
display: none;
font-size: 120%;
@include border-radius(0 0 4px 4px);
position: fixed;
top: 0;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
background-color: $spree-blue;
color: #FFFFFF;
opacity: .8;
font-size: 1rem;
padding: .5rem 1rem;
font-weight: bold;
line-height: 40px;
margin-left: -100px;
padding-top: 15px;
text-align: center;
text-transform: uppercase;
.spinner {
position: absolute;
left: 50%;
width: 30px;
height: 30px;
top: -5px;
margin-left: -15px;
margin-right: 5px;
}
}
// Loading message replacing a table when it's loading for example
#loading {
text-align: center;
padding: 2rem 0;
color: $color-4;
i {
font-size: 2rem;
}
img.spinner {
border: 0px;
width: 100px;
height: 100px;
}
h1 {
margin-top: 20px;
color: inherit;
}
}

View File

@@ -203,19 +203,6 @@ table#listing_enterprise_groups {
}
}
#loading {
text-align: center;
img.spinner {
border: 0px;
width: 100px;
height: 100px;
}
h1 {
margin-top: 20px;
color: gray;
}
}
.field_with_errors > input {
border-color: red;
}

View File

@@ -90,18 +90,6 @@ table.index td.actions {
text-align: left;
}
.orders-loading {
margin-top: 1em;
img {
width: 85px;
}
span {
font-size: 1.2em;
}
}
.index-controls {
button {

View File

@@ -184,6 +184,10 @@ fieldset {
&:hover {
border-color: $color-1;
}
&:first-of-type {
margin-right: 1.25em;
}
}
span.or {

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

@@ -136,6 +136,10 @@
display: flex;
align-items: center;
input[type=radio] {
min-width: 12px;
}
label {
margin-top: 0.3rem;
}
@@ -165,6 +169,43 @@
}
}
.checkout-step3 {
padding-left: 15px;
padding-right: 15px;
.checkout-submit {
margin-top: 0;
margin-bottom: 0;
padding-top: 2rem;
.button {
margin-bottom: 1rem;
}
}
&.sticked {
background-color: $white;
box-shadow: 0 -6px 12px -6px rgba(0, 0, 0, 0.33);
border-left: 1px solid $light-grey;
border-right: 1px solid $light-grey;
border-top: 1px solid $light-grey;
}
@media screen and (max-width: 700px) {
&.sticked {
width: auto;
margin-left: -15px;
margin-right: -15px;
box-shadow: 0 0px 10px 0px rgba(0, 0, 0, 0.33);
.checkout-submit {
width: 100%;
padding-left: 15px;
padding-right: 15px;
}
}
}
}
.checkout-submit {
margin-top: 5rem;
margin-bottom: 5rem;

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

@@ -0,0 +1,20 @@
# frozen_string_literal: false
if AWS::VERSION == "1.67.0"
module AWS
module Core
module Signers
# @api private
class S3
module URI
def self.escape(string)
::URI::RFC2396_Parser.new.escape(string)
end
end
end
end
end
end
else
Rails.logger.warn "The aws-sdk patch needs updating or removing."
end

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

@@ -99,17 +99,4 @@ describe BaseController, type: :controller do
controller.current_order(true)
end
end
it "redirects to shopfront with message if order cycle is expired" do
expect(controller).to receive(:current_order_cycle).and_return(oc)
expect(controller).to receive(:current_order).and_return(order).at_least(:twice)
expect(oc).to receive(:closed?).and_return(true)
expect(order).to receive(:empty!)
expect(order).to receive(:set_order_cycle!).with(nil)
get :index
expect(response).to redirect_to shop_url
expect(flash[:info]).to eq I18n.t('order_cycle_closed')
end
end

View File

@@ -25,6 +25,20 @@ describe CheckoutController, type: :controller do
expect(response).to redirect_to shop_path
end
it "redirects to shopfront with message if order cycle is expired" do
allow(controller).to receive(:current_distributor).and_return(distributor)
expect(controller).to receive(:current_order_cycle).and_return(order_cycle).at_least(:once)
expect(controller).to receive(:current_order).and_return(order).at_least(:once)
expect(order_cycle).to receive(:closed?).and_return(true)
expect(order).to receive(:empty!)
expect(order).to receive(:set_order_cycle!).with(nil)
get :edit
expect(response).to redirect_to shop_url
expect(flash[:info]).to eq I18n.t('order_cycle_closed')
end
it "redirects home with message if hub is not ready for checkout" do
allow(distributor).to receive(:ready_for_checkout?) { false }
allow(order).to receive_messages(distributor: distributor, order_cycle: order_cycle)

View File

@@ -70,6 +70,22 @@ module PaymentGateways
}.to change { Customer.count }.by(1)
end
context "when the order cycle has closed" do
it "redirects to shopfront with message if order cycle is expired" do
allow(controller).to receive(:current_distributor).and_return(distributor)
expect(controller).to receive(:current_order_cycle).and_return(order_cycle)
expect(controller).to receive(:current_order).and_return(order).at_least(:once)
expect(order_cycle).to receive(:closed?).and_return(true)
expect(order).to receive(:empty!)
expect(order).to receive(:set_order_cycle!).with(nil)
get :confirm, params: { payment_intent: "pi_123" }
expect(response).to redirect_to shop_url
expect(flash[:info]).to eq I18n.t('order_cycle_closed')
end
end
context "using split checkout" do
before do
allow(Flipper).to receive(:enabled?).with(:split_checkout) { true }
@@ -236,6 +252,21 @@ module PaymentGateways
expect(payment.cvv_response_message).to be nil
end
end
context "when the order cycle has closed" do
it "should still authorize the payment successfully" do
expect(order).to receive(:process_payments!) do
payment.complete!
end
get :authorize, params: { order_number: order.number, payment_intent: payment_intent }
expect(response).to redirect_to order_path(order)
payment.reload
expect(payment.state).to eq("completed")
expect(payment.cvv_response_message).to be nil
end
end
end
context "when the payment intent response has errors" do

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

@@ -142,6 +142,34 @@ describe SplitCheckoutController, type: :controller do
end
end
context "with payment fees" do
let(:payment_method_with_fee) do
create(:payment_method, :flat_rate, amount: "1.23", distributors: [distributor])
end
let(:checkout_params) do
{
order: {
payments_attributes: [
{ payment_method_id: payment_method_with_fee.id }
]
}
}
end
it "applies the fee and updates the order total" do
put :update, params: params
expect(response).to redirect_to checkout_step_path(:summary)
order.reload
expect(order.state).to eq "confirmation"
expect(order.payments.first.adjustment.amount).to eq 1.23
expect(order.adjustment_total).to eq 1.23
expect(order.total).to eq order.item_total + order.adjustment_total
end
end
context "with a saved credit card" do
let!(:saved_card) { create(:stored_credit_card, user: user) }
let(:checkout_params) do

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

@@ -0,0 +1,72 @@
# frozen_string_literal: false
require 'spec_helper'
module Spree
describe Image do
include FileHelper
let(:file) { Rack::Test::UploadedFile.new(black_logo_file, 'image/png') }
let(:product) { create(:product) }
describe "using local storage" do
it "stores a new image" do
image = Spree::Image.create!(
attachment: file,
viewable: product.master,
)
attachment = image.attachment
expect(attachment.exists?).to eq true
expect(attachment.file?).to eq true
expect(attachment.url).to match %r"^/spree/products/[0-9]+/product/logo-black\.png\?[0-9]+$"
end
end
describe "using AWS S3" do
let(:s3_config) {
{
url: ":s3_alias_url",
storage: :s3,
s3_credentials: {
access_key_id: "A...A",
secret_access_key: "H...H",
},
s3_headers: { "Cache-Control" => "max-age=31557600" },
bucket: "ofn",
s3_protocol: "https",
s3_host_alias: "ofn.s3.us-east-1.amazonaws.com",
# This is for easier testing:
path: "/:id/:style/:basename.:extension",
}
}
before do
attachment_definition = Spree::Image.attachment_definitions[:attachment]
allow(Spree::Image).to receive(:attachment_definitions).and_return(
attachment: attachment_definition.merge(s3_config)
)
end
it "saves a new image when none is present" do
upload_pattern = %r"^https://ofn.s3.amazonaws.com/[0-9]+/(original|mini|small|product|large)/logo-black.png$"
download_pattern = %r"^https://ofn.s3.amazonaws.com/[0-9]+/product/logo-black.png$"
public_url_pattern = %r"^https://ofn.s3.us-east-1.amazonaws.com/[0-9]+/product/logo-black.png\?[0-9]+$"
stub_request(:put, upload_pattern).to_return(status: 200, body: "", headers: {})
stub_request(:head, download_pattern).to_return(status: 200, body: "", headers: {})
image = Spree::Image.create!(
attachment: file,
viewable: product.master,
)
attachment = image.attachment
expect(attachment.exists?).to eq true
expect(attachment.file?).to eq true
expect(attachment.url).to match public_url_pattern
end
end
end
end

View File

@@ -525,6 +525,38 @@ module Spree
expect(product.master).to be_valid
end
end
context "when the product's unit is non-weight" do
before do
product.update_attribute :variant_unit, 'volume'
product.reload
variant.reload
end
it "sets weight to decimal before save if it's integer" do
variant.weight = 1
variant.save!
expect(variant.weight).to eq 1.0
end
it "sets weight to 0.0 before save if it's nil" do
variant.weight = nil
variant.save!
expect(variant.weight).to eq 0.0
end
it "sets weight to 0.0 if input is a non numerical string" do
variant.weight = "BANANAS!"
variant.save!
expect(variant.weight).to eq 0.0
end
it "sets weight to correct decimal value if input is numerical string" do
variant.weight = "2"
variant.save!
expect(variant.weight).to eq 2.0
end
end
end
describe "unit value/description" do

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

@@ -418,7 +418,7 @@ describe '
expect(page).to have_select2 'order_cycle_filter',
with_options: OrderCycle.pluck(:name).unshift("All")
select2_select oc1.name, from: "order_cycle_filter"
expect(page).to have_no_selector "#loading img.spinner"
expect(page).to have_no_selector "#loading i"
expect(page).to have_selector "tr#li_#{li1.id}"
expect(page).to have_no_selector "tr#li_#{li2.id}"
end

View File

@@ -852,10 +852,10 @@ describe '
attach_file 'image-upload', Rails.root.join("public/500.jpg"), visible: false
# Shows spinner whilst loading
expect(page).to have_css "img.spinner", visible: true
expect(page).to have_css ".spinner", visible: true
end
expect(page).to have_no_css "img.spinner", visible: true
expect(page).to have_no_css ".spinner", visible: true
expect(page).to have_no_selector "div.reveal-modal"
within "table#listing_products tr#p_#{product.id}" do

View File

@@ -575,7 +575,7 @@ describe "Product Import", js: true do
I18n.default_locale = original_default_locale
end
it 'returns the header in selected language' do
xit 'returns the header in selected language' do
csv_data = CSV.generate do |csv|
csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type",
"shipping_category"]

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

@@ -223,6 +223,25 @@ describe "As a consumer, I want to checkout my order", js: true do
end
end
context "with a saved address" do
let!(:address_state) do
create(:state, name: "Testville", abbr: "TST", country: DefaultCountry.country )
end
let(:saved_address) do
create(:bill_address, state: address_state, zipcode: "TST01" )
end
before do
user.update_columns bill_address_id: saved_address.id
end
it "pre-fills address details" do
visit checkout_path
expect(page).to have_select "order_bill_address_attributes_state_id", selected: "Testville"
expect(page).to have_field "order_bill_address_attributes_zipcode", with: "TST01"
end
end
context "summary step" do
let(:order) { create(:order_ready_for_confirmation, distributor: distributor) }

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

456
yarn.lock
View File

@@ -2120,27 +2120,27 @@
"@sinonjs/commons" "^1.7.0"
"@storybook/addon-controls@^6.4.13":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-6.4.14.tgz#05a2d58175bf5af00d408386f18bcb2f62da431b"
integrity sha512-12d0Bw0TsueyaQOKMzWTm+G4d78yKXRdX7NP6q6h0HWdqGFdcsuZ60QcQh+GExR9z/M2laDSIijTBZtEJggWGQ==
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-6.4.17.tgz#8d3ac4a71b6c0cd7e863cb813ccc7184fd4c6cdc"
integrity sha512-THUzl+iTBi+joESO09mKbCe3063EVDysDGRHa/f7f6pND8306gLlcVDlFjYDX7F39V6h+1UpdDeNDdBL7yiGjQ==
dependencies:
"@storybook/addons" "6.4.14"
"@storybook/api" "6.4.14"
"@storybook/client-logger" "6.4.14"
"@storybook/components" "6.4.14"
"@storybook/core-common" "6.4.14"
"@storybook/addons" "6.4.17"
"@storybook/api" "6.4.17"
"@storybook/client-logger" "6.4.17"
"@storybook/components" "6.4.17"
"@storybook/core-common" "6.4.17"
"@storybook/csf" "0.0.2--canary.87bc651.0"
"@storybook/node-logger" "6.4.14"
"@storybook/store" "6.4.14"
"@storybook/theming" "6.4.14"
"@storybook/node-logger" "6.4.17"
"@storybook/store" "6.4.17"
"@storybook/theming" "6.4.17"
core-js "^3.8.2"
lodash "^4.17.21"
ts-dedent "^2.0.0"
"@storybook/addon-docs@^6.4.13":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-6.4.14.tgz#0be1faf6d0b4deae93e37a838a2aae197cead74a"
integrity sha512-MIZWfDG80kolo1lOGfMOzQlE3d0I3PBvz04u8v2UMB6k99msC55ZigZcyaKRQs3lwlVM6uUflNVnpTVuTUZNHA==
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-6.4.17.tgz#d5b78554137379e8761f9ffcaa9fcc64c7e41c2b"
integrity sha512-AyCl4chBJErIpGRUbk+bC/Xkvt57H3+pyrFdN7hOuR8erreBaFeFQBAtKZodxq0X4WlQKCKmHYCNZXxNaQOTvw==
dependencies:
"@babel/core" "^7.12.10"
"@babel/generator" "^7.12.11"
@@ -2151,21 +2151,21 @@
"@mdx-js/loader" "^1.6.22"
"@mdx-js/mdx" "^1.6.22"
"@mdx-js/react" "^1.6.22"
"@storybook/addons" "6.4.14"
"@storybook/api" "6.4.14"
"@storybook/builder-webpack4" "6.4.14"
"@storybook/client-logger" "6.4.14"
"@storybook/components" "6.4.14"
"@storybook/core" "6.4.14"
"@storybook/core-events" "6.4.14"
"@storybook/addons" "6.4.17"
"@storybook/api" "6.4.17"
"@storybook/builder-webpack4" "6.4.17"
"@storybook/client-logger" "6.4.17"
"@storybook/components" "6.4.17"
"@storybook/core" "6.4.17"
"@storybook/core-events" "6.4.17"
"@storybook/csf" "0.0.2--canary.87bc651.0"
"@storybook/csf-tools" "6.4.14"
"@storybook/node-logger" "6.4.14"
"@storybook/postinstall" "6.4.14"
"@storybook/preview-web" "6.4.14"
"@storybook/source-loader" "6.4.14"
"@storybook/store" "6.4.14"
"@storybook/theming" "6.4.14"
"@storybook/csf-tools" "6.4.17"
"@storybook/node-logger" "6.4.17"
"@storybook/postinstall" "6.4.17"
"@storybook/preview-web" "6.4.17"
"@storybook/source-loader" "6.4.17"
"@storybook/store" "6.4.17"
"@storybook/theming" "6.4.17"
acorn "^7.4.1"
acorn-jsx "^5.3.1"
acorn-walk "^7.2.0"
@@ -2189,35 +2189,35 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
"@storybook/addons@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.4.14.tgz#45d6937bc2ece33ceadc5358b2a2298d2a0d1e95"
integrity sha512-Snu42ejLyBAh6PWdlrdI72HKN1oKY7q0R9qEID2wk953WrqgGu4URakp14YLxghJCyKTSfGPs6LNZRRI6H5xgA==
"@storybook/addons@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.4.17.tgz#d040db3ddcf72fd9e7df8b8fce2a6dc88578c87e"
integrity sha512-C/hji0Bc7+tssGqaD0JYd/Pz0GM46xbRpdgHSVLInYdhJrb5a9IG6INCbcB8CXeReDKWJCLAaj2+z79Wa96bFQ==
dependencies:
"@storybook/api" "6.4.14"
"@storybook/channels" "6.4.14"
"@storybook/client-logger" "6.4.14"
"@storybook/core-events" "6.4.14"
"@storybook/api" "6.4.17"
"@storybook/channels" "6.4.17"
"@storybook/client-logger" "6.4.17"
"@storybook/core-events" "6.4.17"
"@storybook/csf" "0.0.2--canary.87bc651.0"
"@storybook/router" "6.4.14"
"@storybook/theming" "6.4.14"
"@storybook/router" "6.4.17"
"@storybook/theming" "6.4.17"
"@types/webpack-env" "^1.16.0"
core-js "^3.8.2"
global "^4.4.0"
regenerator-runtime "^0.13.7"
"@storybook/api@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.4.14.tgz#a477646f7e020a362f044d2e614e3d1a86ba8f6f"
integrity sha512-GGGwB5+EquoausTXYx4dnLBBk2sOiS1Z58mDj0swBXCZdjfyUfLyxjxvvb/hl65ltufWP3IdmlKKaLiuARXNtw==
"@storybook/api@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.4.17.tgz#82c3d756c85a65ecd8a3c3d9ce890e581175003a"
integrity sha512-O0ssHVy40t4QD5CNdNESbJo7uZd86UWYrHCFjgeC2gmxrMgBD+ajO34N4HoQFC/F+/84om2/z8RYAGKu/WpoTA==
dependencies:
"@storybook/channels" "6.4.14"
"@storybook/client-logger" "6.4.14"
"@storybook/core-events" "6.4.14"
"@storybook/channels" "6.4.17"
"@storybook/client-logger" "6.4.17"
"@storybook/core-events" "6.4.17"
"@storybook/csf" "0.0.2--canary.87bc651.0"
"@storybook/router" "6.4.14"
"@storybook/router" "6.4.17"
"@storybook/semver" "^7.3.2"
"@storybook/theming" "6.4.14"
"@storybook/theming" "6.4.17"
core-js "^3.8.2"
fast-deep-equal "^3.1.3"
global "^4.4.0"
@@ -2229,10 +2229,10 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
"@storybook/builder-webpack4@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.4.14.tgz#77f45d164f5b93776fa154252706c6b73bd0edc5"
integrity sha512-hRzwdNNLxuyb0XPpvbTSkQuqG2frhog2SsjgPVXorsSMPr95owo9Nq9hp+TnywpvaR9lrPlESzhhv2sSR3blTw==
"@storybook/builder-webpack4@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.4.17.tgz#ad71aaa0a271941e2efe114d5bf7bc8feaa13dcf"
integrity sha512-jE1JehWj5gjLwafGuvV1OyBFVVhBCvv6ESc3QPm+jrsf4ZyB9xliTsnPt3bDggQhWpTEbxgGw7IkVc83ss4AOw==
dependencies:
"@babel/core" "^7.12.10"
"@babel/plugin-proposal-class-properties" "^7.12.1"
@@ -2255,22 +2255,22 @@
"@babel/preset-env" "^7.12.11"
"@babel/preset-react" "^7.12.10"
"@babel/preset-typescript" "^7.12.7"
"@storybook/addons" "6.4.14"
"@storybook/api" "6.4.14"
"@storybook/channel-postmessage" "6.4.14"
"@storybook/channels" "6.4.14"
"@storybook/client-api" "6.4.14"
"@storybook/client-logger" "6.4.14"
"@storybook/components" "6.4.14"
"@storybook/core-common" "6.4.14"
"@storybook/core-events" "6.4.14"
"@storybook/node-logger" "6.4.14"
"@storybook/preview-web" "6.4.14"
"@storybook/router" "6.4.14"
"@storybook/addons" "6.4.17"
"@storybook/api" "6.4.17"
"@storybook/channel-postmessage" "6.4.17"
"@storybook/channels" "6.4.17"
"@storybook/client-api" "6.4.17"
"@storybook/client-logger" "6.4.17"
"@storybook/components" "6.4.17"
"@storybook/core-common" "6.4.17"
"@storybook/core-events" "6.4.17"
"@storybook/node-logger" "6.4.17"
"@storybook/preview-web" "6.4.17"
"@storybook/router" "6.4.17"
"@storybook/semver" "^7.3.2"
"@storybook/store" "6.4.14"
"@storybook/theming" "6.4.14"
"@storybook/ui" "6.4.14"
"@storybook/store" "6.4.17"
"@storybook/theming" "6.4.17"
"@storybook/ui" "6.4.17"
"@types/node" "^14.0.10"
"@types/webpack" "^4.41.26"
autoprefixer "^9.8.6"
@@ -2304,51 +2304,51 @@
webpack-hot-middleware "^2.25.1"
webpack-virtual-modules "^0.2.2"
"@storybook/channel-postmessage@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-6.4.14.tgz#ce719041768ea8c0d64b7edc32ec7c774fba9b19"
integrity sha512-z+fBi/eAAswELWOdlIFI9XXNjyxfguKyqKGSQ7qdz3eFyxeuWnxTa9aZsnLIXpPKY9QPydpBSJcIKUCdN6DbIg==
"@storybook/channel-postmessage@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-6.4.17.tgz#9f439bee440479bfe8f86092701b3b63afc5195a"
integrity sha512-IaVkO/w7bn95Psm1iROlSsc/DHh9RiA7F151VLFD9VTh55qiIfeRssfBXIg3ueGUWm0K+Y9J1jQbcqJoEniMtw==
dependencies:
"@storybook/channels" "6.4.14"
"@storybook/client-logger" "6.4.14"
"@storybook/core-events" "6.4.14"
"@storybook/channels" "6.4.17"
"@storybook/client-logger" "6.4.17"
"@storybook/core-events" "6.4.17"
core-js "^3.8.2"
global "^4.4.0"
qs "^6.10.0"
telejson "^5.3.2"
"@storybook/channel-websocket@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/channel-websocket/-/channel-websocket-6.4.14.tgz#d71a4c8a4b36e2e89a4a3c56af3e0aa50353e02f"
integrity sha512-4Y6TDeYLzItGIaYKo3s6xxSmUF11j96dOX7n74ax45zcMhpp/XwG5i0FU1DtGb5PnhPxg+vJmKa1IgizzaWRYg==
"@storybook/channel-websocket@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/channel-websocket/-/channel-websocket-6.4.17.tgz#780cc68bb3a31069196b35a232013764cc2320a3"
integrity sha512-HtApo/3upDvxSl6VU04F/JznMIltUHeyEqaQNlkqJbQ1VQEHky/M/XJZWT4I/b+nGMXCt0+z0P0ikZ6VZKzFsw==
dependencies:
"@storybook/channels" "6.4.14"
"@storybook/client-logger" "6.4.14"
"@storybook/channels" "6.4.17"
"@storybook/client-logger" "6.4.17"
core-js "^3.8.2"
global "^4.4.0"
telejson "^5.3.2"
"@storybook/channels@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.4.14.tgz#f7a5416c971febd26ed7b03a75d99fd819790e48"
integrity sha512-3QOVxFG6ZAxDXCta1ie4SUPQ3s50yHeuZzVg6uPp+DcC1FrXeDFYBcU9t0j/jrSgbeKcnFHWxmRHNy1BRyWv/A==
"@storybook/channels@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.4.17.tgz#95d05745a96b6059cea26d45aacca3967c401e26"
integrity sha512-C6ON1olkkHc+FaDerkwL1yYGDL1xtFP+eMlm42ZaO06sIT9qv9EkJZ3GU/PNLTeXYMX4OsZl9kjz2whD4rN7gg==
dependencies:
core-js "^3.8.2"
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
"@storybook/client-api@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.4.14.tgz#d2053511971e06d70bba2accfbd1f6c0f2084e2a"
integrity sha512-hqdgE0zKVhcqG/8t/veJRgjsOT076LeKxoA+w2Ga4iU+reIGui/GvLsjvyFFTyOMHVeo2Ze4LW63oTYKF/I5iQ==
"@storybook/client-api@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.4.17.tgz#34476732eb4a698e7dcc774a21feca4529581889"
integrity sha512-qK8Bvsr2KzndAu8RxbBrieNUCltO/ynwtAohJ/29hAg/duf94CZjN0HkuTpQmd4lDip11d9o4Fz5UBWC0zMyOw==
dependencies:
"@storybook/addons" "6.4.14"
"@storybook/channel-postmessage" "6.4.14"
"@storybook/channels" "6.4.14"
"@storybook/client-logger" "6.4.14"
"@storybook/core-events" "6.4.14"
"@storybook/addons" "6.4.17"
"@storybook/channel-postmessage" "6.4.17"
"@storybook/channels" "6.4.17"
"@storybook/client-logger" "6.4.17"
"@storybook/core-events" "6.4.17"
"@storybook/csf" "0.0.2--canary.87bc651.0"
"@storybook/store" "6.4.14"
"@storybook/store" "6.4.17"
"@types/qs" "^6.9.5"
"@types/webpack-env" "^1.16.0"
core-js "^3.8.2"
@@ -2363,23 +2363,23 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
"@storybook/client-logger@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.4.14.tgz#a7aed982407e4146548f9ac4b3af5eba24cd045e"
integrity sha512-4VmFWZxhpeiG5fDhfqAyQbCfXZSBKS4fNKf35ABWiHStZRDndxml8K5WFtmOmMvVzjrGQx8HesenYMawK6xo/Q==
"@storybook/client-logger@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.4.17.tgz#50652859592c489b671f010455b8ce85d21a1b3d"
integrity sha512-awKBTOWHXHBxAIl8a/Zy/BitIw49A+0RnhPGuf8aFAw2Ym/vKR4bI8lRHVPtlR6RIHFp5rC1g32HmCQfKE22Fw==
dependencies:
core-js "^3.8.2"
global "^4.4.0"
"@storybook/components@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.4.14.tgz#546b34fe3feb09e670b76ff71d889bf5f566f1e4"
integrity sha512-M7unerbOnvg+UN7qPxBCBWzK/boVdSSQxRiPAr1OL3M4OyEU8+TNPdQeAG0aF4zqtU0BrsDf4E85EznoMXUiFQ==
"@storybook/components@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.4.17.tgz#5be383682d9538c35c96463723cb17740f105fb6"
integrity sha512-R6imELCWlHWQiprYMeeXLKgUQK4m698G/jvkc1xUxAThpTxwgROTcpw5qnJA0k+wltjGn4t6MBWKHhheGZc6Hg==
dependencies:
"@popperjs/core" "^2.6.0"
"@storybook/client-logger" "6.4.14"
"@storybook/client-logger" "6.4.17"
"@storybook/csf" "0.0.2--canary.87bc651.0"
"@storybook/theming" "6.4.14"
"@storybook/theming" "6.4.17"
"@types/color-convert" "^2.0.0"
"@types/overlayscrollbars" "^1.12.0"
"@types/react-syntax-highlighter" "11.0.5"
@@ -2401,21 +2401,21 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
"@storybook/core-client@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-6.4.14.tgz#8f5dc6fe2295e479225bd396404a43679a15637e"
integrity sha512-e9pzKz52DVhmo8+sUEDvagwGKVqWZ6NQBIt3mBvd79/zXTPkFRnSVitOyYErqhgN1kuwocTg+2BigRr3H0qXaQ==
"@storybook/core-client@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-6.4.17.tgz#baff9629a0723f9485d608c00357d45921a78a0d"
integrity sha512-uXO+DW5XI6fWLtQIBIBlBFeYGsy2qZEe3lxxXwBHwIjsDq53/1CmhEPuzC3jAsy5ddeKC2yEEHUdy3d3wkusIQ==
dependencies:
"@storybook/addons" "6.4.14"
"@storybook/channel-postmessage" "6.4.14"
"@storybook/channel-websocket" "6.4.14"
"@storybook/client-api" "6.4.14"
"@storybook/client-logger" "6.4.14"
"@storybook/core-events" "6.4.14"
"@storybook/addons" "6.4.17"
"@storybook/channel-postmessage" "6.4.17"
"@storybook/channel-websocket" "6.4.17"
"@storybook/client-api" "6.4.17"
"@storybook/client-logger" "6.4.17"
"@storybook/core-events" "6.4.17"
"@storybook/csf" "0.0.2--canary.87bc651.0"
"@storybook/preview-web" "6.4.14"
"@storybook/store" "6.4.14"
"@storybook/ui" "6.4.14"
"@storybook/preview-web" "6.4.17"
"@storybook/store" "6.4.17"
"@storybook/ui" "6.4.17"
airbnb-js-shims "^2.2.1"
ansi-to-html "^0.6.11"
core-js "^3.8.2"
@@ -2427,10 +2427,10 @@
unfetch "^4.2.0"
util-deprecate "^1.0.2"
"@storybook/core-common@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.4.14.tgz#137b935855f0cc785ec55b386312747949e30e99"
integrity sha512-7NRmtcY2INmobsmUUX4afO78RHpyQMO8vboy6H8HRtfcw6fy4zaHoCb7gZZfvvn8gtBWNmwip8I9XK5BpRrh3Q==
"@storybook/core-common@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.4.17.tgz#bd2b14cfd1473f5f31f40c747afb62ea4a4ada6e"
integrity sha512-aOSG5Yvd8eoZsjvVlk7sS8iRXWT/dleHoHPXtKmHJnGcIZ1dcgr4wZqoOvL8dGhNNoU4Wx9dkJepqHD0+E/UgA==
dependencies:
"@babel/core" "^7.12.10"
"@babel/plugin-proposal-class-properties" "^7.12.1"
@@ -2453,7 +2453,7 @@
"@babel/preset-react" "^7.12.10"
"@babel/preset-typescript" "^7.12.7"
"@babel/register" "^7.12.1"
"@storybook/node-logger" "6.4.14"
"@storybook/node-logger" "6.4.17"
"@storybook/semver" "^7.3.2"
"@types/node" "^14.0.10"
"@types/pretty-hrtime" "^1.0.0"
@@ -2482,29 +2482,29 @@
util-deprecate "^1.0.2"
webpack "4"
"@storybook/core-events@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.4.14.tgz#37293c0fce703f2643cec6f24fc6ef7c40e30ded"
integrity sha512-9QFltg2mxTDjMBfmVtFHtrAEPY/i0oVp2kVdTWo6g05cPffYKAjNUnUVjUl7yiqcQmdEcdqUUQ0ut3xgmcYi/A==
"@storybook/core-events@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.4.17.tgz#ad70c883673a2060f1c7c4aa8f5926fc14119f4a"
integrity sha512-k6wNjQLZZ8A/rt4gLz0M4ebTORKYYz2B9hZ3LvPJftNVqv+bTFAV4KVks6bBlvbJWpJ+eCPEyfeSP9Np2QIFMQ==
dependencies:
core-js "^3.8.2"
"@storybook/core-server@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.4.14.tgz#0bef36e1203eb56e1c9bbf7f02122500c8f7d534"
integrity sha512-SzO8SaLTZ36Q4PNhJD4XJjlnonbR2Os0gzTknDBbwyIRPUtFUdk6isSG14RM5yYWPM0QQIs9og5ztSPX58YZlw==
"@storybook/core-server@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.4.17.tgz#16f31635565c19248a45a793a7c2150597f3a4cc"
integrity sha512-wXYF4VD2EJ/6uFK+wAo/TgUyfD/lfMzzbAw2gBZAjYp7y7Zwj3svGqUfkFuPQG0/E9gmQfEmlyhTPPZImBFeBg==
dependencies:
"@discoveryjs/json-ext" "^0.5.3"
"@storybook/builder-webpack4" "6.4.14"
"@storybook/core-client" "6.4.14"
"@storybook/core-common" "6.4.14"
"@storybook/core-events" "6.4.14"
"@storybook/builder-webpack4" "6.4.17"
"@storybook/core-client" "6.4.17"
"@storybook/core-common" "6.4.17"
"@storybook/core-events" "6.4.17"
"@storybook/csf" "0.0.2--canary.87bc651.0"
"@storybook/csf-tools" "6.4.14"
"@storybook/manager-webpack4" "6.4.14"
"@storybook/node-logger" "6.4.14"
"@storybook/csf-tools" "6.4.17"
"@storybook/manager-webpack4" "6.4.17"
"@storybook/node-logger" "6.4.17"
"@storybook/semver" "^7.3.2"
"@storybook/store" "6.4.14"
"@storybook/store" "6.4.17"
"@types/node" "^14.0.10"
"@types/node-fetch" "^2.5.7"
"@types/pretty-hrtime" "^1.0.0"
@@ -2537,18 +2537,18 @@
webpack "4"
ws "^8.2.3"
"@storybook/core@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-6.4.14.tgz#c20a1432f22603eb2d3523389ff1311fffbba24f"
integrity sha512-41WNDXKMZuCKnvbLBBYCd1+ip4uJ4AGeCOhmp/KZK7TgkitJ0JrvyRgnbpXR8bAMiOv2Hh9t9Vmi5D3QZ8COlg==
"@storybook/core@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-6.4.17.tgz#61892e2eb484a44fc9d69515c3068b1f64173327"
integrity sha512-wquJcEebw9kXJ7pThcmEsDNK0ykd3ir0uL5tkBzPGNIj7dozpzy24Fo9JSr0rNWHNtE7JczdIAQTcumowLTDig==
dependencies:
"@storybook/core-client" "6.4.14"
"@storybook/core-server" "6.4.14"
"@storybook/core-client" "6.4.17"
"@storybook/core-server" "6.4.17"
"@storybook/csf-tools@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-6.4.14.tgz#c5112e17f07dae4c7b922aefd45dccbbc9e49803"
integrity sha512-mRFsIhzFA2JBeUqdvl6+WM6HmHXaWGLbCgalzGqX65i1pSvhmC3jHh0OTTypMj9XneWH6/cHQh7LvivYbjJ8Cg==
"@storybook/csf-tools@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-6.4.17.tgz#e7d2b9de95317d706657c294d7beee1d7b307ec4"
integrity sha512-GnaO1dX4wTvMKBthbbHLHcrDTXwZ7PooZmT1fTCeokzaobZzyv1cUtF1hlPQa3zA75kRE5AznJ0jmBVhHe0/9Q==
dependencies:
"@babel/core" "^7.12.10"
"@babel/generator" "^7.12.11"
@@ -2575,20 +2575,20 @@
dependencies:
lodash "^4.17.15"
"@storybook/manager-webpack4@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/manager-webpack4/-/manager-webpack4-6.4.14.tgz#b8ca3a11d0fb18ef6ca3e58e1c36b2eb8226ccbf"
integrity sha512-j565G7vZLBXK60J1hiZhbeZ6K48y8CMMZCcIihqsFv/4jj0kI3Ba4IhCrOkHiqiRM89mRu5/Ga3DnHTBvIYIEA==
"@storybook/manager-webpack4@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/manager-webpack4/-/manager-webpack4-6.4.17.tgz#8fc6d5dba0587446defe78ced67b7033886c4c47"
integrity sha512-ekHudBR8FVSE475YQZZs9sqwou7YqFv03hNVOcvIJ36cZBgMbSkG8q50cK4uru2xCOedTK15SKIoFZQQ77cmQQ==
dependencies:
"@babel/core" "^7.12.10"
"@babel/plugin-transform-template-literals" "^7.12.1"
"@babel/preset-react" "^7.12.10"
"@storybook/addons" "6.4.14"
"@storybook/core-client" "6.4.14"
"@storybook/core-common" "6.4.14"
"@storybook/node-logger" "6.4.14"
"@storybook/theming" "6.4.14"
"@storybook/ui" "6.4.14"
"@storybook/addons" "6.4.17"
"@storybook/core-client" "6.4.17"
"@storybook/core-common" "6.4.17"
"@storybook/node-logger" "6.4.17"
"@storybook/theming" "6.4.17"
"@storybook/ui" "6.4.17"
"@types/node" "^14.0.10"
"@types/webpack" "^4.41.26"
babel-loader "^8.0.0"
@@ -2617,10 +2617,10 @@
webpack-dev-middleware "^3.7.3"
webpack-virtual-modules "^0.2.2"
"@storybook/node-logger@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.4.14.tgz#2e96f4e3e06c78c3d065e59818515209122d9ae4"
integrity sha512-mowC0adx4hLtCqGMQKRfNmiRYAL2PYdk3ojc91qzIKNrjSYnE4U8d9qlw5WLx1PKEnZVji3+QiYfNHpA/8PoKw==
"@storybook/node-logger@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.4.17.tgz#14fe3091b2030413c2f43f0de3e9408b27591d9c"
integrity sha512-gymFKjmOdi9fAJCaM4C8I/5Go4hPsOAcVNixpjAQYsvNQQZ1Yjm2zcSdD+QOuLJ36NTxgOFxT4ESbC2AfSjyqA==
dependencies:
"@types/npmlog" "^4.1.2"
chalk "^4.1.0"
@@ -2628,24 +2628,24 @@
npmlog "^5.0.1"
pretty-hrtime "^1.0.3"
"@storybook/postinstall@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-6.4.14.tgz#e8f7925529d4955783660f409deee1e907897b2b"
integrity sha512-nLHV+BdDKFAZWU1CA/o3zRCNT3+tVWesERqkO9kJLURwqHkfU1yyv5WNILyUsvlwwJCFdDOEdXupC1RR7E6Gkg==
"@storybook/postinstall@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-6.4.17.tgz#f8a858b45e39d012ceb2054c1a8417f66dbb73e8"
integrity sha512-KHjhb3yRrATgYrVNeU/rdMRWXzXxFVFHtrtTo63D8LAoogX2g02DzLmtgqcoddp/JnSeHRtwEIbXnAExGYwb3Q==
dependencies:
core-js "^3.8.2"
"@storybook/preview-web@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/preview-web/-/preview-web-6.4.14.tgz#4d7035d5aa0e8c41c9a2ff21c2a3b3cbae9f3688"
integrity sha512-3E++OYz+OCyJBIchkNCJRtxEU7XNDBdIvKRTCx48X+Uv5qoLeCpXiXOSK/42LlraWZkfBs56yHv9VSqJoQ8VwA==
"@storybook/preview-web@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/preview-web/-/preview-web-6.4.17.tgz#d29bbfa8f66428ef5e2202c4c364c1892d8cbf7b"
integrity sha512-fJIE/LO7I09w334AH71ojRpIiHLQrBUidkZlIQbjEmHn/GZBTePlf3CevrERA12FbCLoUbeS5nadk2dEg6YnUw==
dependencies:
"@storybook/addons" "6.4.14"
"@storybook/channel-postmessage" "6.4.14"
"@storybook/client-logger" "6.4.14"
"@storybook/core-events" "6.4.14"
"@storybook/addons" "6.4.17"
"@storybook/channel-postmessage" "6.4.17"
"@storybook/client-logger" "6.4.17"
"@storybook/core-events" "6.4.17"
"@storybook/csf" "0.0.2--canary.87bc651.0"
"@storybook/store" "6.4.14"
"@storybook/store" "6.4.17"
ansi-to-html "^0.6.11"
core-js "^3.8.2"
global "^4.4.0"
@@ -2657,12 +2657,12 @@
unfetch "^4.2.0"
util-deprecate "^1.0.2"
"@storybook/router@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.4.14.tgz#46fd46eadafc0d6b647be13702704c5fcf8f11e3"
integrity sha512-5+tePyINtwPYm4izgOBZ2sX2ViWtfmmO2vwOAPlWWEGzsRosVQsGMdZv1R8rk4Jl/TotMjlTmd8I1/BufEeIeQ==
"@storybook/router@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.4.17.tgz#d53c4f9a4ccaa21a7bbe8d875a1a81c9dba2a6f2"
integrity sha512-GLhzth83BB2BbUkM/+ld2JITIbDQtzFLs/CnZZQKq6aR93Kou6VK2epHnIwrPyWbP6rsGavR/8L/UWeBdwwTrQ==
dependencies:
"@storybook/client-logger" "6.4.14"
"@storybook/client-logger" "6.4.17"
core-js "^3.8.2"
fast-deep-equal "^3.1.3"
global "^4.4.0"
@@ -2683,19 +2683,19 @@
find-up "^4.1.0"
"@storybook/server@^6.4.13":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/server/-/server-6.4.14.tgz#c1f0293fdef084fd63473eaf128fda19351b68fa"
integrity sha512-DfehTFQ5yd9UoGoEj2AnyTg7RupgqQpzusj8gN42DxcKF/5Jpp2tzkN71imw0shzL5SL0KFs36YVvJQMUKXQ4Q==
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/server/-/server-6.4.17.tgz#e30c89d45550b430ad25cfc357b6c8b29525c90a"
integrity sha512-KZ4f/ArfxVQwKsZ8t62zG9LGcbbuI130PAXbr4u9AodUaRlIg2bBPpBZ0P2peR0UJOikgXgFLu7K6p2MLK61fw==
dependencies:
"@storybook/addons" "6.4.14"
"@storybook/api" "6.4.14"
"@storybook/client-api" "6.4.14"
"@storybook/core" "6.4.14"
"@storybook/core-common" "6.4.14"
"@storybook/addons" "6.4.17"
"@storybook/api" "6.4.17"
"@storybook/client-api" "6.4.17"
"@storybook/core" "6.4.17"
"@storybook/core-common" "6.4.17"
"@storybook/csf" "0.0.2--canary.87bc651.0"
"@storybook/node-logger" "6.4.14"
"@storybook/preview-web" "6.4.14"
"@storybook/store" "6.4.14"
"@storybook/node-logger" "6.4.17"
"@storybook/preview-web" "6.4.17"
"@storybook/store" "6.4.17"
"@types/webpack-env" "^1.16.0"
core-js "^3.8.2"
global "^4.4.0"
@@ -2707,13 +2707,13 @@
ts-dedent "^2.0.0"
yaml-loader "^0.6.0"
"@storybook/source-loader@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/source-loader/-/source-loader-6.4.14.tgz#d1e6c2df0918e4867b3e4b5ce748685938f77d87"
integrity sha512-3hqVTK5+rQFK7Jf6/jYO/24daYIMn9L1vCAo9xSFgy999OMw7967ZmVMGMgVkOh7GQSZmzt3kMonv4bDmIGJMw==
"@storybook/source-loader@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/source-loader/-/source-loader-6.4.17.tgz#d17e73f88f8c2a714fe129bf66ad692b4d4e0c97"
integrity sha512-OAETI21mL/jwmb9e/JtFDIsLWoOOWOAIm3Cj89XHQz/5VkYljZxdh2icb6xDHR8PtEaXj4+sBWQUG3L+a/a9QQ==
dependencies:
"@storybook/addons" "6.4.14"
"@storybook/client-logger" "6.4.14"
"@storybook/addons" "6.4.17"
"@storybook/client-logger" "6.4.17"
"@storybook/csf" "0.0.2--canary.87bc651.0"
core-js "^3.8.2"
estraverse "^5.2.0"
@@ -2723,14 +2723,14 @@
prettier ">=2.2.1 <=2.3.0"
regenerator-runtime "^0.13.7"
"@storybook/store@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/store/-/store-6.4.14.tgz#2ec5601e1c40a27f164b570d4c2b84c57d3a4115"
integrity sha512-D9KoJuNvwb9mEQD60GTPYSbQuXWZQHE8RBxCq7d7Qu46mrhlsNTOwt09lIgmuM3jAVto3FxnXY4U81RwJza7tg==
"@storybook/store@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/store/-/store-6.4.17.tgz#a68b687628b82f8ca0ef2c74f5d65f5a30e94f86"
integrity sha512-0rWk8u7gtzBOp5NvuIrL6abBHaDxax7e+yBPvU9tR0GZ7X0ALhOhJFRIo+lW9sZTUrcuSinOJ8Acyb0ZvnYCkg==
dependencies:
"@storybook/addons" "6.4.14"
"@storybook/client-logger" "6.4.14"
"@storybook/core-events" "6.4.14"
"@storybook/addons" "6.4.17"
"@storybook/client-logger" "6.4.17"
"@storybook/core-events" "6.4.17"
"@storybook/csf" "0.0.2--canary.87bc651.0"
core-js "^3.8.2"
fast-deep-equal "^3.1.3"
@@ -2744,15 +2744,15 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
"@storybook/theming@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.4.14.tgz#f034914eb1853a80f588c7c141d47af0595f6d1f"
integrity sha512-kqmXNnIoOSAS4cgr9PitMgVrOps725O99eTsJNxB6J1Ide0CsA5v2tV6AmQn/scnpCQNr8uSjZerNlEcl/ensg==
"@storybook/theming@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.4.17.tgz#f0a03d2d3239638ac171e97a8f089ee2656a8287"
integrity sha512-7+U72/VdhoMb00q1URMzdTW3OYHJogro2i2hScgKR+ndL4/dtSmetJ/1z9PuoFxLxHgdLKcwMAV0fZAjEYlhCA==
dependencies:
"@emotion/core" "^10.1.1"
"@emotion/is-prop-valid" "^0.8.6"
"@emotion/styled" "^10.0.27"
"@storybook/client-logger" "6.4.14"
"@storybook/client-logger" "6.4.17"
core-js "^3.8.2"
deep-object-diff "^1.1.0"
emotion-theming "^10.0.27"
@@ -2762,21 +2762,21 @@
resolve-from "^5.0.0"
ts-dedent "^2.0.0"
"@storybook/ui@6.4.14":
version "6.4.14"
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.4.14.tgz#59f08ac8d8eb782fa13fc9a8dd715222c96bf234"
integrity sha512-nZsd8GXzYwmmTjZUB7pJMh+Q1fST0d2lFkhDHakxLaPLwumibw9NHJ7bRWYHFlAVYpD0c2+POP3FpOW5Bjby1A==
"@storybook/ui@6.4.17":
version "6.4.17"
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.4.17.tgz#926aa1dbb4d3f9a28afe6edd6a0e5cb667be8e19"
integrity sha512-vBYV8PmvhYgMjjTRLtOHIisGqr1nfajAgOC+wfYvGLbF0npVEt5PfDieG1LTRc1OaItWLpKKJcByqSfL/y9Qow==
dependencies:
"@emotion/core" "^10.1.1"
"@storybook/addons" "6.4.14"
"@storybook/api" "6.4.14"
"@storybook/channels" "6.4.14"
"@storybook/client-logger" "6.4.14"
"@storybook/components" "6.4.14"
"@storybook/core-events" "6.4.14"
"@storybook/router" "6.4.14"
"@storybook/addons" "6.4.17"
"@storybook/api" "6.4.17"
"@storybook/channels" "6.4.17"
"@storybook/client-logger" "6.4.17"
"@storybook/components" "6.4.17"
"@storybook/core-events" "6.4.17"
"@storybook/router" "6.4.17"
"@storybook/semver" "^7.3.2"
"@storybook/theming" "6.4.14"
"@storybook/theming" "6.4.17"
copy-to-clipboard "^3.3.1"
core-js "^3.8.2"
core-js-pure "^3.8.2"
@@ -8227,9 +8227,9 @@ karma-jasmine@~0.3.8:
integrity sha1-W2RXeRrZuJqhc/B54+vhuMgFI2w=
karma@~6.3.11:
version "6.3.12"
resolved "https://registry.yarnpkg.com/karma/-/karma-6.3.12.tgz#fe6347f027385fc16da1a9bb87d766e2d25981c6"
integrity sha512-qwIG+oB2YmHx4hjvYSRMNzL3YWAJ9baHaLAxiP7biFNkfpwYTUTtPck0joFpucalNLzMr+7z/FX1uY/kl8DV9A==
version "6.3.13"
resolved "https://registry.yarnpkg.com/karma/-/karma-6.3.13.tgz#ff58622c4f913508dbf86506a5fc614be0e98137"
integrity sha512-64lbtHBnM/sG7XKvitSQVlImKYNugyjw4K2e8cZYrlZm0GwoWrfsrkzgS/KWY/r3+fVM/2xvT/JN6MyfdttxLw==
dependencies:
body-parser "^1.19.0"
braces "^3.0.2"
@@ -8243,7 +8243,7 @@ karma@~6.3.11:
http-proxy "^1.18.1"
isbinaryfile "^4.0.8"
lodash "^4.17.21"
log4js "^6.3.0"
log4js "^6.4.1"
mime "^2.5.2"
minimatch "^3.0.4"
qjobs "^1.2.0"
@@ -8413,10 +8413,10 @@ lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
log4js@^6.3.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.4.0.tgz#3f63ccfc8033c83cd617a4d2d50e48be5944eae9"
integrity sha512-ysc/XUecZJuN8NoKOssk3V0cQ29xY4fra6fnigZa5VwxFsCsvdqsdnEuAxNN89LlHpbE4KUD3zGcn+kFqonSVQ==
log4js@^6.4.1:
version "6.4.1"
resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.4.1.tgz#9d3a8bf2c31c1e213fe3fc398a6053f7a2bc53e8"
integrity sha512-iUiYnXqAmNKiIZ1XSAitQ4TmNs8CdZYTAWINARF3LjnsLN8tY5m0vRwd6uuWj/yNY0YHxeZodnbmxKFUOM2rMg==
dependencies:
date-format "^4.0.3"
debug "^4.3.3"