diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index fd4b9ed7e74..ee314686204 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,6 +6,12 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Layout/EmptyLineAfterGuardClause: + Exclude: + - 'app/controllers/spree/admin/orders_controller.rb' + # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). Layout/EmptyLines: @@ -35,15 +41,8 @@ Lint/EmptyClass: - 'spec/lib/reports/report_loader_spec.rb' # Offense count: 1 -# Configuration parameters: AllowComments. -Lint/EmptyFile: - Exclude: - - 'spec/lib/open_food_network/enterprise_injection_data_spec.rb' - -# Offense count: 2 Lint/FloatComparison: Exclude: - - 'app/models/product_import/entry_validator.rb' - 'app/models/spree/gateway/pay_pal_express.rb' # Offense count: 1 @@ -85,14 +84,13 @@ Lint/UselessMethodDefinition: Exclude: - 'app/models/spree/gateway.rb' -# Offense count: 24 +# Offense count: 23 # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max. Metrics/AbcSize: Exclude: - 'app/controllers/admin/enterprises_controller.rb' - 'app/controllers/payment_gateways/paypal_controller.rb' - 'app/controllers/spree/admin/payments_controller.rb' - - 'app/controllers/spree/admin/taxons_controller.rb' - 'app/controllers/spree/admin/variants_controller.rb' - 'app/controllers/spree/orders_controller.rb' - 'app/helpers/spree/admin/navigation_helper.rb' @@ -137,6 +135,7 @@ Metrics/ClassLength: - 'app/controllers/admin/resource_controller.rb' - 'app/controllers/admin/subscriptions_controller.rb' - 'app/controllers/application_controller.rb' + - 'app/controllers/checkout_controller.rb' - 'app/controllers/payment_gateways/paypal_controller.rb' - 'app/controllers/spree/admin/orders_controller.rb' - 'app/controllers/spree/admin/payment_methods_controller.rb' @@ -162,7 +161,6 @@ Metrics/ClassLength: - 'app/models/spree/user.rb' - 'app/models/spree/variant.rb' - 'app/models/spree/zone.rb' - - 'app/reflexes/admin/orders_reflex.rb' - 'app/serializers/api/cached_enterprise_serializer.rb' - 'app/serializers/api/enterprise_shopfront_serializer.rb' - 'app/services/cart_service.rb' @@ -178,12 +176,11 @@ Metrics/ClassLength: - 'lib/reporting/reports/enterprise_fee_summary/scope.rb' - 'lib/reporting/reports/xero_invoices/base.rb' -# Offense count: 32 +# Offense count: 30 # Configuration parameters: AllowedMethods, AllowedPatterns, Max. Metrics/CyclomaticComplexity: Exclude: - 'app/controllers/admin/enterprises_controller.rb' - - 'app/controllers/spree/admin/taxons_controller.rb' - 'app/controllers/spree/orders_controller.rb' - 'app/helpers/checkout_helper.rb' - 'app/helpers/order_cycles_helper.rb' @@ -199,7 +196,6 @@ Metrics/CyclomaticComplexity: - 'app/models/spree/preferences/preferable_class_methods.rb' - 'app/models/spree/return_authorization.rb' - 'app/models/spree/tax_rate.rb' - - 'app/models/spree/variant.rb' - 'app/models/spree/zone.rb' - 'lib/open_food_network/enterprise_issue_validator.rb' - 'lib/reporting/reports/xero_invoices/base.rb' @@ -208,13 +204,12 @@ Metrics/CyclomaticComplexity: - 'lib/spree/localized_number.rb' - 'spec/models/product_importer_spec.rb' -# Offense count: 24 +# Offense count: 23 # Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: Exclude: - 'app/controllers/admin/enterprises_controller.rb' - 'app/controllers/payment_gateways/paypal_controller.rb' - - 'app/controllers/spree/admin/taxons_controller.rb' - 'app/controllers/spree/orders_controller.rb' - 'app/helpers/spree/admin/navigation_helper.rb' - 'app/models/spree/ability.rb' @@ -293,19 +288,17 @@ Metrics/ParameterLists: - 'spec/support/controller_requests_helper.rb' - 'spec/system/admin/reports_spec.rb' -# Offense count: 4 +# Offense count: 3 # Configuration parameters: AllowedMethods, AllowedPatterns, Max. Metrics/PerceivedComplexity: Exclude: - - 'app/controllers/spree/admin/taxons_controller.rb' - 'app/models/enterprise_relationship.rb' - 'app/models/spree/ability.rb' - 'app/models/spree/order/checkout.rb' -# Offense count: 8 +# Offense count: 7 Naming/AccessorMethodName: Exclude: - - 'app/controllers/spree/admin/taxonomies_controller.rb' - 'app/mailers/producer_mailer.rb' - 'app/models/spree/order.rb' - 'app/services/checkout/post_checkout_actions.rb' @@ -353,7 +346,7 @@ Naming/VariableNumber: - 'spec/models/spree/tax_rate_spec.rb' - 'spec/requests/api/orders_spec.rb' -# Offense count: 142 +# Offense count: 141 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: ResponseMethods. # ResponseMethods: response, last_response @@ -557,7 +550,7 @@ RSpecRails/InferredSpecType: - 'spec/requests/voucher_adjustments_spec.rb' - 'spec/routing/stripe_spec.rb' -# Offense count: 22 +# Offense count: 21 # Configuration parameters: IgnoreScopes, Include. # Include: app/models/**/*.rb Rails/InverseOf: @@ -572,11 +565,10 @@ Rails/InverseOf: - 'app/models/spree/price.rb' - 'app/models/spree/product.rb' - 'app/models/spree/stock_item.rb' - - 'app/models/spree/taxonomy.rb' - 'app/models/spree/variant.rb' - 'app/models/subscription_line_item.rb' -# Offense count: 35 +# Offense count: 36 # Configuration parameters: Include. # Include: app/controllers/**/*.rb, app/mailers/**/*.rb Rails/LexicallyScopedActionFilter: @@ -720,7 +712,7 @@ Style/GlobalStdStream: - 'lib/tasks/subscriptions/debug.rake' - 'lib/tasks/subscriptions/test.rake' -# Offense count: 12 +# Offense count: 10 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowSplatArgument. Style/HashConversion: @@ -728,9 +720,7 @@ Style/HashConversion: - 'app/controllers/admin/column_preferences_controller.rb' - 'app/controllers/admin/variant_overrides_controller.rb' - 'app/controllers/spree/admin/products_controller.rb' - - 'app/models/order_cycle.rb' - 'app/models/product_import/product_importer.rb' - - 'app/models/spree/shipping_method.rb' - 'app/serializers/api/admin/exchange_serializer.rb' - 'app/services/variants_stock_levels.rb' - 'spec/controllers/admin/inventory_items_controller_spec.rb' diff --git a/Gemfile b/Gemfile index fb414a745ab..cf5d2d6155a 100644 --- a/Gemfile +++ b/Gemfile @@ -101,7 +101,6 @@ gem 'redis' gem 'sidekiq' gem 'sidekiq-scheduler' -gem "cable_ready" gem "stimulus_reflex" gem "turbo_power" diff --git a/Gemfile.lock b/Gemfile.lock index 3bd6dacdf6d..e2ec8532307 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -869,7 +869,6 @@ DEPENDENCIES bootsnap bugsnag bullet - cable_ready cancancan (~> 1.15.0) capybara catalog! diff --git a/app/components/confirm_modal_component.rb b/app/components/confirm_modal_component.rb index 8149a729e32..7dd75eb10aa 100644 --- a/app/components/confirm_modal_component.rb +++ b/app/components/confirm_modal_component.rb @@ -8,7 +8,7 @@ def initialize( controller: nil, message: nil, confirm_actions: nil, - confirm_reflexes: nil, + form_url: nil, confirm_button_class: :primary, confirm_button_text: I18n.t('js.admin.modals.confirm'), cancel_button_text: I18n.t('js.admin.modals.cancel'), @@ -17,7 +17,7 @@ def initialize( super(id:, close_button: true) @confirm_actions = confirm_actions @reflex = reflex - @confirm_reflexes = confirm_reflexes + @form_url = form_url @controller = controller @message = message @confirm_button_class = confirm_button_class diff --git a/app/components/confirm_modal_component/confirm_modal_component.html.haml b/app/components/confirm_modal_component/confirm_modal_component.html.haml index 1184602a675..d73415fa96f 100644 --- a/app/components/confirm_modal_component/confirm_modal_component.html.haml +++ b/app/components/confirm_modal_component/confirm_modal_component.html.haml @@ -1,10 +1,19 @@ %div{ id: @id, "data-controller": "modal #{@controller}", "data-action": "keyup@document->modal#closeIfEscapeKey", "data-#{@controller}-reflex-value": @reflex } .reveal-modal-bg.fade{ "data-modal-target": "background", "data-action": "click->modal#close" } .reveal-modal.fade.tiny.modal-component{ "data-modal-target": "modal" } - = content + - if @confirm_actions + = content - = render @message if @message + = render @message if @message - %div{ class: "modal-actions #{@actions_alignment_class}" } - %input{ class: "button icon-plus #{close_button_class}", type: 'button', value: @cancel_button_text, "data-action": "click->modal#close" } - %input{ id: 'modal-confirm-button', class: "button icon-plus #{@confirm_button_class}", type: 'button', value: @confirm_button_text, "data-action": @confirm_actions, "data-reflex": @confirm_reflexes } + %div{ class: "modal-actions #{@actions_alignment_class}" } + %input{ class: "button icon-plus #{close_button_class}", type: 'button', value: @cancel_button_text, "data-action": "click->modal#close" } + %input{ id: 'modal-confirm-button', class: "button icon-plus #{@confirm_button_class}", type: 'button', value: @confirm_button_text, "data-action": @confirm_actions } + - elsif @form_url + = form_with(url: @form_url, method: :post, data: { turbo: true, controller: 'bulk-actions' }) do + = content + %p{ class: "modal-actions #{@actions_alignment_class}" } + %button.button.secondary{ type: "button", 'data-action': 'click->modal#close' } + = @cancel_button_text + %button.button.primary{ type: 'submit' } + = @confirm_button_text \ No newline at end of file diff --git a/app/components/ship_order_component.html.haml b/app/components/ship_order_component.html.haml index be05f354e18..2938608ce3d 100644 --- a/app/components/ship_order_component.html.haml +++ b/app/components/ship_order_component.html.haml @@ -1,8 +1,15 @@ -= render ConfirmModalComponent.new(id: dom_id(@order, :ship), confirm_reflexes: "click->Admin::OrdersReflex#ship", controller: "orders", reflex: "Admin::Orders#ship") do - %div{class: "margin-bottom-30"} - %p= t('spree.admin.orders.shipment.mark_as_shipped_message_html') - %div{class: "margin-bottom-30"} - = hidden_field_tag :id, @order.id - = label_tag do - = check_box_tag :send_shipment_email, "1", true - = t('spree.admin.orders.shipment.mark_as_shipped_label_message') += render ModalComponent.new(id: dom_id(@order, :ship), modal_class: 'tiny', close_button: false) do + = form_with(url: helpers.ship_admin_order_url(@order), method: :post, data: { turbo: true }) do + %div{class: "margin-bottom-30"} + %p= t('spree.admin.orders.shipment.mark_as_shipped_message_html') + %div{class: "margin-bottom-30"} + = hidden_field_tag :id, @order.id + = hidden_field_tag :current_page, @current_page + = label_tag do + = check_box_tag :send_shipment_email, "1", true + = t('spree.admin.orders.shipment.mark_as_shipped_label_message') + %p.modal-actions.justify-space-around + %button.button.secondary{ type: "button", 'data-action': 'click->modal#close' } + = t('js.admin.modals.cancel') + %button.button.primary{ type: 'submit' } + = t('js.admin.modals.confirm') diff --git a/app/components/ship_order_component.rb b/app/components/ship_order_component.rb index 89f500afcb9..05d2c13b297 100644 --- a/app/components/ship_order_component.rb +++ b/app/components/ship_order_component.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true class ShipOrderComponent < ViewComponent::Base - def initialize(order:) + def initialize(order:, current_page:) @order = order + @current_page = current_page end end diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index e5b35473acd..410eb7b0e3d 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -7,14 +7,13 @@ module Admin class EnterprisesController < Admin::ResourceController include GeocodeEnterpriseAddress - include CablecarResponses include Pagy::Backend # These need to run before #load_resource so that @object is initialised with sanitised values prepend_before_action :override_owner, only: :create prepend_before_action :override_sells, only: :create - before_action :load_countries, except: [:index, :register, :check_permalink] + before_action :load_countries, except: [:index, :register, :check_permalink, :remove_logo] before_action :load_methods_and_fees, only: [:edit, :update] before_action :load_groups, only: [:new, :edit, :update, :create] before_action :load_taxons, only: [:new, :edit, :update, :create] @@ -51,8 +50,10 @@ def edit @enterprise.is_primary_producer = params[:is_primary_producer] @enterprise.sells = params[:enterprise_sells] - render cable_ready: cable_car.morph("#side_menu", partial("admin/shared/side_menu")) - .morph("#permalink", partial("admin/enterprises/form/permalink")) + respond_to do |format| + format.html { head :ok } + format.turbo_stream { render :edit } + end end def welcome @@ -141,6 +142,33 @@ def visible end end + def remove_logo + # delete the white_label_logo_link attribute as well since it has no meaning without the logo + @object.update!(white_label_logo: nil, white_label_logo_link: "") + + f = ActionView::Helpers::FormBuilder.new(:enterprise, @object, view_context, {}) + + respond_to do |format| + format.html do + flash[:success] = I18n.t("admin.enterprises.form.white_label.remove_logo_success") + redirect_to edit_admin_enterprise_path + end + format.turbo_stream do + flash.now[:success] = I18n.t("admin.enterprises.form.white_label.remove_logo_success") + render turbo_stream: [ + turbo_stream.replace( + 'white_label_panel', + partial: "admin/enterprises/form/white_label", locals: { f:, enterprise: @object } + ), + turbo_stream.append( + "flashes", + partial: 'admin/shared/flashes', locals: { flashes: flash } + ) + ] + end + end + end + protected def delete_custom_tab diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c31e748a302..0e535c0e9f4 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -9,7 +9,6 @@ require 'open_food_network/referer_parser' class ApplicationController < ActionController::Base - include CablecarResponses include Pagy::Backend include RequestTimeouts diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index efd8e80eeb5..0c6e9568e08 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -10,7 +10,6 @@ class CheckoutController < BaseController include CheckoutCallbacks include CheckoutSteps include OrderCompletion - include CablecarResponses include WhiteLabel helper 'terms_and_conditions' @@ -49,7 +48,7 @@ def update rescue Spree::Core::GatewayError => e flash[:error] = I18n.t(:spree_gateway_error_flash_for_checkout, error: e.message) @order.update_column(:state, "payment") - render cable_ready: cable_car.redirect_to(url: checkout_step_path(:payment)) + redirect_to checkout_step_path(:payment) end private @@ -57,9 +56,12 @@ def update def render_error flash.now[:error] ||= I18n.t('checkout.errors.saving_failed') - render status: :unprocessable_entity, cable_ready: cable_car. - replace("#checkout", partial("checkout/checkout")). - replace("#flashes", partial("shared/flashes", locals: { flashes: flash })) + respond_to do |format| + format.html { head :unprocessable_entity } + format.turbo_stream do + render :render_error, status: :unprocessable_entity + end + end end def check_payments_adjustments @@ -87,7 +89,7 @@ def redirect_to_payment_gateway return unless selected_payment_method&.external_gateway? return unless (redirect_url = selected_payment_method.external_payment_url(order: @order)) - render cable_ready: cable_car.redirect_to(url: redirect_url) + redirect_to redirect_url true end diff --git a/app/controllers/concerns/cablecar_responses.rb b/app/controllers/concerns/cablecar_responses.rb deleted file mode 100644 index b98d158daed..00000000000 --- a/app/controllers/concerns/cablecar_responses.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module CablecarResponses - extend ActiveSupport::Concern - - included do - include CableReady::Broadcaster - end - - private - - def partial(path, options = {}) - { html: render_to_string(partial: path, **options) } - end -end diff --git a/app/controllers/concerns/order_stock_check.rb b/app/controllers/concerns/order_stock_check.rb index 13ff145ae89..d769bc9ff9f 100644 --- a/app/controllers/concerns/order_stock_check.rb +++ b/app/controllers/concerns/order_stock_check.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module OrderStockCheck - include CablecarResponses extend ActiveSupport::Concern def valid_order_line_items? @@ -27,13 +26,7 @@ def check_order_cycle_expiry current_order.set_order_cycle! nil flash[:info] = I18n.t('order_cycle_closed') - respond_to do |format| - format.cable_ready { - render status: :see_other, cable_ready: cable_car.redirect_to(url: main_app.shop_path) - } - format.json { render json: { path: main_app.shop_path }, status: :see_other } - format.html { redirect_to main_app.shop_path, status: :see_other } - end + redirect_to main_app.shop_path end private diff --git a/app/controllers/spree/admin/orders_controller.rb b/app/controllers/spree/admin/orders_controller.rb index a73adf58c4f..69e75fa0ef3 100644 --- a/app/controllers/spree/admin/orders_controller.rb +++ b/app/controllers/spree/admin/orders_controller.rb @@ -11,15 +11,12 @@ class OrdersController < Spree::Admin::BaseController before_action :load_order, only: [:edit, :update, :fire, :resend, :invoice, :print] before_action :load_distribution_choices, only: [:new, :create, :edit, :update] before_action :require_distributor_abn, only: :invoice - before_action :restore_saved_query!, only: :index - - respond_to :html, :json + before_action :authorize_order, only: [:capture, :ship] def index orders = SearchOrders.new(search_params, spree_current_user).orders @pagy, @orders = pagy(orders, items: params[:per_page] || 15) - - update_search_results if searching? + @stored_query = search_params.to_query end def new @@ -84,9 +81,7 @@ def resend Spree::OrderMailer.confirm_email_for_customer(@order.id, true).deliver_later flash[:success] = t('admin.orders.order_email_resent') - respond_with(@order) do |format| - format.html { redirect_back(fallback_location: spree.admin_dashboard_path) } - end + redirect_back(fallback_location: spree.admin_dashboard_path) end def invoice @@ -94,9 +89,7 @@ def invoice current_user_id: spree_current_user.id ).deliver_later flash[:success] = t('admin.orders.invoice_email_sent') - respond_with(@order) { |format| - format.html { redirect_to spree.edit_admin_order_path(@order) } - } + redirect_to spree.edit_admin_order_path(@order) end def print @@ -112,26 +105,129 @@ def print render_with_wicked_pdf InvoiceRenderer.new.args(@order, spree_current_user) end - private + def capture + payment_capture = ::Orders::CaptureService.new(@order) + unless (@saved = payment_capture.call) + message = payment_capture.gateway_error || I18n.t(:payment_processing_failed) + end - def line_items_present? - return true if @order.line_items.any? + respond_to do |format| + format.html do + flash[:error] = message unless @saved + redirect_to admin_orders_path + end + format.turbo_stream do + flash.now[:error] = message unless @saved + render 'spree/admin/orders/capture' + end + end + end - @order.errors.add(:line_items, Spree.t('errors.messages.blank')) - false + def ship + @order.send_shipment_email = false unless params[:send_shipment_email] + @order.send_shipment_email + if @order.ship + if params[:current_page] != 'index' + return redirect_back fallback_location: admin_orders_path + end + @shipped = true + end + + respond_to do |format| + format.html do + flash[:error] = I18n.t("api.orders.failed_to_update") unless @shipped + redirect_back fallback_location: admin_orders_path + end + format.turbo_stream do + flash.now[:error] = I18n.t("api.orders.failed_to_update") unless @shipped + render 'spree/admin/orders/ship' + end + end end - def update_search_results - session[:admin_orders_search] = search_params + def bulk_invoice + visible_orders = bulk_load_orders - render cable_ready: cable_car.inner_html( - "#orders-index", - partial("spree/admin/orders/table", locals: { pagy: @pagy, orders: @orders }) + return if notify_if_abn_related_issue(visible_orders) + + @file_id = "#{Time.zone.now.to_i}-#{SecureRandom.hex(2)}" + + # Preserve order of bulk_ids. + # The ids are supplied in the sequence of the orders screen and may be + # sorted, for example by last name of the customer. + visible_order_ids = params[:bulk_ids].map(&:to_i) & visible_orders.pluck(:id) + + BulkInvoiceJob.perform_later( + visible_order_ids, + "tmp/invoices/#{@file_id}.pdf", + channel: SessionChannel.for_request(request), + current_user_id: spree_current_user.id ) + respond_to do |format| + format.html { redirect_to admin_orders_path } + format.turbo_stream { render 'spree/admin/orders/bulk_invoice' } + end end - def searching? - params[:q].present? && request.format.symbol == :cable_ready + def cancel_orders + @cancelled_orders = ::Orders::BulkCancelService.new(params, spree_current_user).call + + respond_to do |format| + format.html { redirect_to admin_orders_path } + format.turbo_stream { render 'spree/admin/orders/cancel_orders' } + end + end + + def resend_confirmation_emails + editable_orders.where(id: params[:bulk_ids]).find_each do |order| + next unless can? :resend, order + + Spree::OrderMailer.confirm_email_for_customer(order.id, true).deliver_later + end + + message = t("admin.resend_confirmation_emails_feedback", count: params[:bulk_ids].count) + + respond_to do |format| + format.html do + flash[:success] = message + redirect_to admin_orders_path + end + format.turbo_stream do + flash.now[:success] = message + render 'spree/admin/orders/resend_confirmation_emails' + end + end + end + + def send_invoices + count = 0 + editable_orders.invoiceable.where(id: params[:bulk_ids]).find_each do |o| + next unless o.distributor.can_invoice? + + Spree::OrderMailer.invoice_email(o.id, + current_user_id: current_spree_user.id).deliver_later + count += 1 + end + + respond_to do |format| + format.html do + flash[:success] = t("admin.send_invoice_feedback", count:) + redirect_to admin_orders_path + end + format.turbo_stream do + flash.now[:success] = t("admin.send_invoice_feedback", count:) + render 'spree/admin/orders/send_invoices' + end + end + end + + private + + def line_items_present? + return true if @order.line_items.any? + + @order.errors.add(:line_items, Spree.t('errors.messages.blank')) + false end def search_params @@ -144,13 +240,6 @@ def default_filters { q: { completed_at_not_null: 1, s: "completed_at desc" } } end - def restore_saved_query! - return unless request.format.html? - - @_params = ActionController::Parameters.new(session[:admin_orders_search] || {}) - @stored_query = search_params.to_query - end - def on_update @order.recreate_all_fees! @@ -174,6 +263,71 @@ def load_order authorize! action, @order end + def authorize_order + @order = Spree::Order.find_by(number: params[:id]) + authorize! :admin, @order + end + + def set_param_for_controller + redirect_back fallback_location: admin_orders_path + params[:id] = @order.number + end + + def bulk_load_orders + editable_orders.invoiceable.where(id: params[:bulk_ids]) + end + + def notify_if_abn_related_issue(orders) + return false unless abn_required? + + distributors = distributors_without_abn(orders) + return false if distributors.empty? + + render_business_number_required_error(distributors) + true + end + + def abn_required? + Spree::Config.enterprise_number_required_on_invoices? + end + + def distributors_without_abn(orders) + abn = if OpenFoodNetwork::FeatureToggle.enabled?(:invoices) + [nil, ""] + else + [nil] + end + Enterprise.where( + id: orders.select(:distributor_id), + abn:, + ) + end + + def render_business_number_required_error(distributors) + distributor_names = distributors.pluck(:name) + + respond_to do |format| + format.html do + flash[:error] = I18n.t(:must_have_valid_business_number, + enterprise_name: distributor_names.join(", ")) + redirect_to admin_orders_path + end + format.turbo_stream do + flash[:error] = I18n.t(:must_have_valid_business_number, + enterprise_name: distributor_names.join(", ")) + render turbo_stream: + turbo_stream.append( + "flashes", + partial: 'admin/shared/flashes', locals: { flashes: flash } + ) + end + end + end + + def editable_orders + Permissions::Order.new(current_spree_user).editable_orders + end + def model_class Spree::Order end diff --git a/app/controllers/spree/orders_controller.rb b/app/controllers/spree/orders_controller.rb index af0738b17e2..c84030ef128 100644 --- a/app/controllers/spree/orders_controller.rb +++ b/app/controllers/spree/orders_controller.rb @@ -4,7 +4,6 @@ module Spree class OrdersController < ::BaseController include OrderCyclesHelper include Rails.application.routes.url_helpers - include CablecarResponses include WhiteLabel layout 'darkswarm' @@ -107,8 +106,7 @@ def cancel else flash[:error] = I18n.t(:orders_could_not_cancel) end - render status: :found, - cable_ready: cable_car.redirect_to(url: request.referer || main_app.order_path(@order)) + redirect_to request.referer || main_app.order_path(@order) end private diff --git a/app/controllers/spree/user_sessions_controller.rb b/app/controllers/spree/user_sessions_controller.rb index c22c6162fd5..129af35fb8c 100644 --- a/app/controllers/spree/user_sessions_controller.rb +++ b/app/controllers/spree/user_sessions_controller.rb @@ -9,7 +9,6 @@ class UserSessionsController < Devise::SessionsController include Spree::Core::ControllerHelpers::Auth include Spree::Core::ControllerHelpers::Common include Spree::Core::ControllerHelpers::Order - include CablecarResponses helper 'spree/base' @@ -24,14 +23,15 @@ def create if spree_user_signed_in? flash[:success] = t('devise.success.logged_in_succesfully') - render cable_ready: cable_car.redirect_to( - url: return_url_or_default(after_sign_in_path_for(spree_current_user)) - ) + redirect_to after_sign_in_path_for(spree_current_user) else - render status: :unauthorized, cable_ready: cable_car.inner_html( - "#login-feedback", - partial("layouts/alert", locals: { type: "alert", message: t('devise.failure.invalid') }) - ) + @message = t('devise.failure.invalid') + respond_to do |format| + format.html { head :unauthorized } + format.turbo_stream do + render :create, status: :unauthorized + end + end end end @@ -60,11 +60,19 @@ def email_unconfirmed? end def render_unconfirmed_response - render status: :unprocessable_entity, cable_ready: cable_car.inner_html( - "#login-feedback", - partial("layouts/alert", locals: { type: "alert", message: t(:email_unconfirmed), - unconfirmed: true, tab: "login" }) - ) + message = t(:email_unconfirmed) + local_values = { type: "alert", message:, unconfirmed: true, + tab: "login", email: params.dig(:spree_user, :email) } + respond_to do |format| + format.html { head :unprocessable_entity } + format.turbo_stream do + render turbo_stream: [ + turbo_stream.update( + "login-feedback", partial: "layouts/alert", locals: local_values + ) + ], status: :unprocessable_entity + end + end end def ensure_valid_locale_persisted diff --git a/app/controllers/spree/users_controller.rb b/app/controllers/spree/users_controller.rb index 0aae64116d1..2119cbd9f33 100644 --- a/app/controllers/spree/users_controller.rb +++ b/app/controllers/spree/users_controller.rb @@ -4,7 +4,6 @@ module Spree class UsersController < ::BaseController include Spree::Core::ControllerHelpers include I18nHelper - include CablecarResponses layout 'darkswarm' @@ -31,13 +30,10 @@ def registered_email registered = Spree::User.find_by(email: params[:email]).present? if registered - render status: :ok, cable_ready: cable_car. - inner_html( - "#login-feedback", - partial("layouts/alert", - locals: { type: "alert", message: t('devise.failure.already_registered') }) - ). - dispatch_event(name: "login:modal:open") + respond_to do |format| + format.html { head :ok } + format.turbo_stream { :registered_email } + end else head :not_found end @@ -48,12 +44,12 @@ def create if @user.save flash[:success] = t('devise.user_registrations.spree_user.signed_up_but_unconfirmed') - render cable_ready: cable_car.redirect_to(url: main_app.root_path) + redirect_to main_app.root_path else - render status: :unprocessable_entity, cable_ready: cable_car.morph( - "#signup-tab", - partial("layouts/signup_tab", locals: { signup_form_user: @user }) - ) + respond_to do |format| + format.html { head :unprocessable_entity } + format.turbo_stream { render :create, status: :unprocessable_entity } + end end end @@ -98,14 +94,10 @@ def user_params end def render_alert_timestamp_error_message - render cable_ready: cable_car.inner_html( - "#signup-feedback", - partial("layouts/alert", - locals: { - type: "alert", - message: InvisibleCaptcha.timestamp_error_message - }) - ) + respond_to do |format| + format.html { head :ok } + format.turbo_stream { render :render_alert_timestamp_error_message } + end end end end diff --git a/app/controllers/user_confirmations_controller.rb b/app/controllers/user_confirmations_controller.rb index d8b6b221fe4..c3caf491578 100644 --- a/app/controllers/user_confirmations_controller.rb +++ b/app/controllers/user_confirmations_controller.rb @@ -3,7 +3,6 @@ class UserConfirmationsController < DeviseController # Needed for access to current_ability, so we can authorize! actions include Spree::Core::ControllerHelpers::Auth - include CablecarResponses # GET /resource/confirmation?confirmation_token=abcdef def show @@ -29,11 +28,8 @@ def create set_flash_message(:error, :confirmation_not_sent) end else - render cable_ready: cable_car.inner_html( - "##{params[:tab] || 'forgot'}-feedback", - partial("layouts/alert", - locals: { type: "success", message: t("devise.confirmations.send_instructions") }) - ) + flash.now[:sucess] = t("devise.confirmations.send_instructions") + render 'spree/user_confirmations/create' return end diff --git a/app/controllers/user_passwords_controller.rb b/app/controllers/user_passwords_controller.rb index b0ad3970d1d..26e4bee4ff1 100644 --- a/app/controllers/user_passwords_controller.rb +++ b/app/controllers/user_passwords_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class UserPasswordsController < Spree::UserPasswordsController - include CablecarResponses - layout 'darkswarm' def create @@ -11,31 +9,37 @@ def create self.resource = resource_class.send_reset_password_instructions(raw_params[resource_name]) if resource.errors.empty? - render cable_ready: cable_car.inner_html( - "#forgot-feedback", - partial("layouts/alert", locals: { type: "success", message: t(:password_reset_sent) }) - ) + @message = t(:password_reset_sent) + @type = :success + respond_to do |format| + format.html { head :ok } + format.turbo_stream { render :create } + end else - render status: :not_found, cable_ready: cable_car.inner_html( - "#forgot-feedback", - partial("layouts/alert", locals: { type: "alert", message: t(:email_not_found) }) - ) + @type = :alert + @message = t(:email_not_found) + respond_to do |format| + format.html { head :not_found } + format.turbo_stream { render :create, status: :not_found } + end end end private def render_unconfirmed_response - render status: :unprocessable_entity, cable_ready: cable_car.inner_html( - "#forgot-feedback", - partial("layouts/alert", - locals: { type: "alert", message: t(:email_unconfirmed), - unconfirmed: true, tab: "forgot" }) - ) + @type = :alert + @message = t(:email_unconfirmed) + @unconfirmed = true + @tab = 'forgot' + respond_to do |format| + format.html { head :unprocessable_entity } + format.turbo_stream { render :create, status: :unprocessable_entity } + end end def user_unconfirmed? - user = Spree::User.find_by(email: params.dig(:spree_user, :email)) - user && !user.confirmed? + @user = Spree::User.find_by(email: params.dig(:spree_user, :email)) + @user && !@user.confirmed? end end diff --git a/app/controllers/voucher_adjustments_controller.rb b/app/controllers/voucher_adjustments_controller.rb index c4f5fdf5fb4..ebe8ee7dfa3 100644 --- a/app/controllers/voucher_adjustments_controller.rb +++ b/app/controllers/voucher_adjustments_controller.rb @@ -60,24 +60,19 @@ def add_voucher end def update_payment_section - render cable_ready: cable_car.replace( - selector: "#checkout-payment-methods", - html: render_to_string(partial: "checkout/payment", locals: { step: "payment" }) - ) + respond_to do |format| + format.html { head :ok } + format.turbo_stream { render :update_payment_section } + end end def render_error flash.now[:error] = @order.errors.full_messages.to_sentence - render status: :unprocessable_entity, cable_ready: cable_car. - replace("#flashes", partial("shared/flashes", locals: { flashes: flash })). - replace( - "#voucher-section", - partial( - "checkout/voucher_section", - locals: { order: @order, voucher_adjustment: @order.voucher_adjustments.first } - ) - ) + respond_to do |format| + format.html { head :unprocessable_entity } + format.turbo_stream { render :render_error, status: :unprocessable_entity } + end end def voucher_params diff --git a/app/jobs/bulk_invoice_job.rb b/app/jobs/bulk_invoice_job.rb index c77eb077f9e..f2b4ab8b825 100644 --- a/app/jobs/bulk_invoice_job.rb +++ b/app/jobs/bulk_invoice_job.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class BulkInvoiceJob < ApplicationJob - include CableReady::Broadcaster delegate :render, to: ActionController::Base attr_reader :options @@ -40,13 +39,14 @@ def generate_invoice(order) def broadcast(filepath, channel) file_id = filepath.split("/").last.split(".").first - cable_ready[channel]. - inner_html( + ActionCable.server.broadcast( + channel, + { selector: "#bulk_invoices_modal .modal-content", html: render(partial: "spree/admin/orders/bulk/invoice_link", locals: { invoice_url: "/admin/orders/invoices/#{file_id}" }) - ). - broadcast + } + ) end def ensure_directory_exists(filepath) diff --git a/app/jobs/connect_app_job.rb b/app/jobs/connect_app_job.rb index fa1b802d052..5a10e6f7c4b 100644 --- a/app/jobs/connect_app_job.rb +++ b/app/jobs/connect_app_job.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class ConnectAppJob < ApplicationJob - include CableReady::Broadcaster - def perform(app, token, channel: nil) url = I18n.t("connect_app.url") event = "connect-app" @@ -23,6 +21,6 @@ def perform(app, token, channel: nil) locals: { enterprise:, connected_app: enterprise.connected_apps.discover_regen.first }, ) - cable_ready[channel].morph(selector:, html:).broadcast + ActionCable.server.broadcast(channel, { selector:, html: }) end end diff --git a/app/jobs/report_job.rb b/app/jobs/report_job.rb index 0367ddd2596..42b1a29ebca 100644 --- a/app/jobs/report_job.rb +++ b/app/jobs/report_job.rb @@ -2,7 +2,6 @@ # Renders a report and stores it in a given blob. class ReportJob < ApplicationJob - include CableReady::Broadcaster delegate :render, to: ActionController::Base before_perform :enable_active_storage_urls @@ -39,21 +38,25 @@ def email_result(user, blob) end def broadcast_result(channel, format, blob) - cable_ready[channel] - .inner_html( + ActionCable.server.broadcast( + channel, + { selector: "#report-go", html: Spree::Admin::BaseController.helpers.button(I18n.t(:go), "report__submit-btn") - ).inner_html( - selector: "#report-table", - html: actioncable_content(format, blob) - ).broadcast + } + ) + + ActionCable.server.broadcast( + channel, + { selector: "#report-table", html: actioncable_content(format, blob) } + ) end def broadcast_error(channel) - cable_ready[channel].inner_html( - selector: "#report-table", - html: I18n.t("report_job.report_failed") - ).broadcast + ActionCable.server.broadcast( + channel, + { selector: "#report-table", html: I18n.t("report_job.report_failed") } + ) end def actioncable_content(format, blob) diff --git a/app/reflexes/admin/orders_reflex.rb b/app/reflexes/admin/orders_reflex.rb deleted file mode 100644 index bc952506c25..00000000000 --- a/app/reflexes/admin/orders_reflex.rb +++ /dev/null @@ -1,161 +0,0 @@ -# frozen_string_literal: true - -module Admin - class OrdersReflex < ApplicationReflex - before_reflex :authorize_order, only: [:capture, :ship] - - def capture - payment_capture = Orders::CaptureService.new(@order) - - if payment_capture.call - cable_ready.replace(selector: dom_id(@order), - html: render(partial: "spree/admin/orders/table_row", - locals: { order: @order.reload, success: true })) - morph :nothing - else - flash[:error] = payment_capture.gateway_error || I18n.t(:payment_processing_failed) - morph_admin_flashes - end - end - - def ship - @order.send_shipment_email = false unless params[:send_shipment_email] - if @order.ship - paths = %w[edit customer payments adjustments invoices return_authorizations].freeze - return set_param_for_controller if Regexp.union(paths).match? request.url - - morph dom_id(@order), render(partial: "spree/admin/orders/table_row", - locals: { order: @order.reload, success: true }) - else - flash[:error] = I18n.t("api.orders.failed_to_update") - morph_admin_flashes - end - end - - def bulk_invoice(params) - visible_orders = bulk_load_orders(params) - - return if notify_if_abn_related_issue(visible_orders) - - file_id = "#{Time.zone.now.to_i}-#{SecureRandom.hex(2)}" - - cable_ready.append( - selector: "#orders-index", - html: render(partial: "spree/admin/orders/bulk/invoice_modal", - locals: { invoice_url: "/admin/orders/invoices/#{file_id}" }) - ).broadcast - - # Preserve order of bulk_ids. - # The ids are supplied in the sequence of the orders screen and may be - # sorted, for example by last name of the customer. - visible_order_ids = params[:bulk_ids].map(&:to_i) & visible_orders.pluck(:id) - - BulkInvoiceJob.perform_later( - visible_order_ids, - "tmp/invoices/#{file_id}.pdf", - channel: SessionChannel.for_request(request), - current_user_id: current_user.id - ) - - morph :nothing - end - - def cancel_orders(params) - cancelled_orders = Orders::BulkCancelService.new(params, current_user).call - - cable_ready.dispatch_event(name: "modal:close") - - cancelled_orders.each do |order| - cable_ready.replace( - selector: dom_id(order), - html: render(partial: "spree/admin/orders/table_row", locals: { order: }) - ) - end - - cable_ready.broadcast - morph :nothing - end - - def resend_confirmation_emails(params) - editable_orders.where(id: params[:bulk_ids]).find_each do |order| - next unless can? :resend, order - - Spree::OrderMailer.confirm_email_for_customer(order.id, true).deliver_later - end - - success("admin.resend_confirmation_emails_feedback", params[:bulk_ids].count) - end - - def send_invoices(params) - count = 0 - editable_orders.invoiceable.where(id: params[:bulk_ids]).find_each do |o| - next unless o.distributor.can_invoice? - - Spree::OrderMailer.invoice_email(o.id, current_user_id: current_user.id).deliver_later - count += 1 - end - - success("admin.send_invoice_feedback", count) - end - - private - - def authorize_order - id = element.dataset[:id] || params[:id] - @order = Spree::Order.find_by(id:) - authorize! :admin, @order - end - - def success(i18n_key, count) - flash[:success] = I18n.t(i18n_key, count:) - cable_ready.dispatch_event(name: "modal:close") - morph_admin_flashes - end - - def editable_orders - Permissions::Order.new(current_user).editable_orders - end - - def set_param_for_controller - params[:id] = @order.number - end - - def render_business_number_required_error(distributors) - distributor_names = distributors.pluck(:name) - - flash[:error] = I18n.t(:must_have_valid_business_number, - enterprise_name: distributor_names.join(", ")) - morph_admin_flashes - end - - def bulk_load_orders(params) - editable_orders.invoiceable.where(id: params[:bulk_ids]) - end - - def notify_if_abn_related_issue(orders) - return false unless abn_required? - - distributors = distributors_without_abn(orders) - return false if distributors.empty? - - render_business_number_required_error(distributors) - true - end - - def abn_required? - Spree::Config.enterprise_number_required_on_invoices? - end - - def distributors_without_abn(orders) - abn = if OpenFoodNetwork::FeatureToggle.enabled?(:invoices) - [nil, ""] - else - [nil] - end - Enterprise.where( - id: orders.select(:distributor_id), - abn:, - ) - end - end -end diff --git a/app/reflexes/white_label_reflex.rb b/app/reflexes/white_label_reflex.rb deleted file mode 100644 index 32aa6618bc8..00000000000 --- a/app/reflexes/white_label_reflex.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -class WhiteLabelReflex < ApplicationReflex - include EnterpriseConcern - delegate :view_context, to: :controller - - def remove_logo - @enterprise.update!(white_label_logo: nil) - # delete the white_label_logo_link attribute as well since it has no meaning without the logo - @enterprise.update!(white_label_logo_link: "") - - f = ActionView::Helpers::FormBuilder.new(:enterprise, @enterprise, view_context, {}) - - html = render(partial: "admin/enterprises/form/white_label", - locals: { f:, enterprise: @enterprise }) - morph "#white_label_panel", html - - flash[:success] = I18n.t("admin.enterprises.form.white_label.remove_logo_success") - cable_ready.dispatch_event(name: "modal:close") - morph_admin_flashes - end -end diff --git a/app/views/admin/enterprises/edit.turbo_stream.haml b/app/views/admin/enterprises/edit.turbo_stream.haml new file mode 100644 index 00000000000..921b635e593 --- /dev/null +++ b/app/views/admin/enterprises/edit.turbo_stream.haml @@ -0,0 +1,4 @@ += turbo_stream.replace 'side_menu' do + = render partial: 'admin/shared/side_menu' += turbo_stream.replace 'permalink' do + = render partial: 'admin/enterprises/form/permalink' diff --git a/app/views/admin/enterprises/form/_white_label.html.haml b/app/views/admin/enterprises/form/_white_label.html.haml index 4c74ed6885c..1946e404b5e 100644 --- a/app/views/admin/enterprises/form/_white_label.html.haml +++ b/app/views/admin/enterprises/form/_white_label.html.haml @@ -29,9 +29,13 @@ - if @object.white_label_logo.present? - = render ConfirmModalComponent.new(id: "remove_logo", confirm_reflexes: "click->WhiteLabel#remove_logo" ) do - .margin-bottom-30 + = render ModalComponent.new(id: "remove_logo", close_button: false, modal_class: 'tiny') do + .content.margin-bottom-30 = t('.remove_logo_confirm') + %p.modal-actions.justify-space-around + = link_to t('js.admin.modals.confirm'), remove_logo_admin_enterprise_path, class: 'button primary', data: { turbo: true, turbo_method: :patch, action: 'click->modal#close' } + %button.button.primary{ type: "button", 'data-action': 'click->modal#close' } + = t('js.admin.modals.cancel') // Hide groups tab boolean attribute diff --git a/app/views/admin/shared/_tooltip_button.html.haml b/app/views/admin/shared/_tooltip_button.html.haml index 3eebca4671e..ea2cb67a600 100644 --- a/app/views/admin/shared/_tooltip_button.html.haml +++ b/app/views/admin/shared/_tooltip_button.html.haml @@ -1,8 +1,8 @@ %div{ "data-controller": "tooltip" } - if local_assigns[:shipment] - %button{class: button_class,"data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "ship_order_#{reflex_data_id}", "data-id": reflex_data_id, "data-tooltip-target": "element" } + %button{class: button_class,"data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "ship_order_#{data_id}", "data-id": data_id, "data-tooltip-target": "element" } - else - %button{class: button_class, "data-reflex": button_reflex, "data-id": reflex_data_id, "data-tooltip-target": "element" } + %button{class: button_class, "data-id": data_id, "data-tooltip-target": "element" } .tooltip-container .tooltip{"data-tooltip-target": "tooltip"} = sanitize tooltip_text diff --git a/app/views/checkout/_details.html.haml b/app/views/checkout/_details.html.haml index 56146d19c09..4c386c7dfed 100644 --- a/app/views/checkout/_details.html.haml +++ b/app/views/checkout/_details.html.haml @@ -1,4 +1,4 @@ -= form_with url: checkout_update_path(checkout_step), model: @order, method: :put, data: { remote: "true" } do |f| += form_with url: checkout_update_path(checkout_step), model: @order, method: :put, data: { turbo: true } do |f| .medium-6 = f.fields :bill_address, model: @order.bill_address do |bill_address| %div.checkout-substep diff --git a/app/views/checkout/_payment.html.haml b/app/views/checkout/_payment.html.haml index 516d2025bb4..ff6cc14e5f3 100644 --- a/app/views/checkout/_payment.html.haml +++ b/app/views/checkout/_payment.html.haml @@ -3,7 +3,7 @@ %div.checkout-substep = render partial: "checkout/voucher_section", locals: { order: @order, voucher_adjustment: @order.voucher_adjustments.first } - = form_with url: checkout_update_path(local_assigns[:step] || checkout_step), model: @order, method: :put, data: { remote: "true" } do |f| + = form_with url: checkout_update_path(local_assigns[:step] || checkout_step), model: @order, method: :put, data: { turbo: "true" } do |f| %div.checkout-substep{"data-controller": "paymentmethod"} %div.checkout-title = t("checkout.step2.payment_method.title") diff --git a/app/views/checkout/_summary.html.haml b/app/views/checkout/_summary.html.haml index 682f02639ea..c5698d447fa 100644 --- a/app/views/checkout/_summary.html.haml +++ b/app/views/checkout/_summary.html.haml @@ -1,4 +1,4 @@ -= form_with url: checkout_update_path(checkout_step), model: @order, method: :put, data: { remote: "true", 'guest-checkout-target': 'summary' } do |f| += form_with url: checkout_update_path(checkout_step), model: @order, method: :put, data: { turbo: "true", 'guest-checkout-target': 'summary' } do |f| .summary-main = render partial: "checkout/already_ordered" if show_bought_items? && checkout_step?(:summary) .checkout-substep diff --git a/app/views/checkout/_voucher_section.html.haml b/app/views/checkout/_voucher_section.html.haml index 0f7fb12f531..cff918377c3 100644 --- a/app/views/checkout/_voucher_section.html.haml +++ b/app/views/checkout/_voucher_section.html.haml @@ -2,13 +2,13 @@ .checkout-title = t("checkout.step2.voucher.apply_voucher") .checkout-input{"data-controller": "toggle-control"} - = form_with url: voucher_adjustments_path, model: @order, method: :post, data: { remote: true } do |form| + = form_with url: voucher_adjustments_path, model: @order, method: :post, data: { turbo: true } do |form| - if voucher_adjustment.present? .two-columns-inputs.voucher %span.button.voucher-added %i.ofn-i_051-check-big = t("checkout.step2.voucher.voucher", voucher_amount: voucher_adjustment.originator.display_value) - = link_to t("checkout.step2.voucher.remove_code"), voucher_adjustment_path(id: voucher_adjustment.id), method: "delete", data: { confirm: t("checkout.step2.voucher.confirm_delete") } + = link_to t("checkout.step2.voucher.remove_code"), voucher_adjustment_path(id: voucher_adjustment.id), data: { turbo_method: "delete", turbo_confirm: t("checkout.step2.voucher.confirm_delete") } - # This might not be true, ie payment method including a fee which wouldn't be covered by voucher or tax implication raising total to be bigger than the voucher amount ? - if voucher_adjustment.originator.amount > order.pre_discount_total && voucher_adjustment.originator.is_a?(Vouchers::FlatRate) @@ -22,4 +22,4 @@ = form.error_message_on :voucher_code %div.checkout-input - = form.submit t("checkout.step2.voucher.apply"), disabled: true, class: "button cancel voucher-button", "data-disable-with": false, data: { "toggle-control-target": "control" } + = form.submit t("checkout.step2.voucher.apply"), disabled: true, class: "button cancel voucher-button", data: { "toggle-control-target": "control" } diff --git a/app/views/checkout/render_error.turbo_stream.haml b/app/views/checkout/render_error.turbo_stream.haml new file mode 100644 index 00000000000..04cd8fb3dc4 --- /dev/null +++ b/app/views/checkout/render_error.turbo_stream.haml @@ -0,0 +1,4 @@ += turbo_stream.replace "flashes" do + = render(partial: 'shared/flashes', locals: { flashes: flash }) += turbo_stream.replace "checkout" do + = render(partial: 'checkout/checkout') \ No newline at end of file diff --git a/app/views/layouts/_alert.html.haml b/app/views/layouts/_alert.html.haml index 36db8b7e314..be1bc76ba9f 100644 --- a/app/views/layouts/_alert.html.haml +++ b/app/views/layouts/_alert.html.haml @@ -1,5 +1,6 @@ .alert-box{ class: "#{type}" } = message - if local_assigns[:unconfirmed] - %a{ "data-action": "login-modal#resend_confirmation", "data-tab": local_assigns[:tab] } + - params = { spree_user: { email: email }, tab: local_assigns[:tab] } + = link_to spree_user_confirmation_path(params), params:, data: { turbo_method: :post } do = t('devise.confirmations.resend_confirmation_email') diff --git a/app/views/layouts/_forgot_tab.html.haml b/app/views/layouts/_forgot_tab.html.haml index 47ad3c03d2f..80a9727b600 100644 --- a/app/views/layouts/_forgot_tab.html.haml +++ b/app/views/layouts/_forgot_tab.html.haml @@ -1,5 +1,5 @@ #forgot-tab - = form_with url: spree_user_password_path, scope: :spree_user, data: { remote: "true" } do |form| + = form_with url: spree_user_password_path, scope: :spree_user, data: { turbo: true } do |form| .row .large-12.columns#forgot-feedback diff --git a/app/views/layouts/_login_tab.html.haml b/app/views/layouts/_login_tab.html.haml index a80a39acce7..9e965a82704 100644 --- a/app/views/layouts/_login_tab.html.haml +++ b/app/views/layouts/_login_tab.html.haml @@ -1,5 +1,5 @@ #login-content - = form_with url: spree_user_session_path, scope: :spree_user, data: { remote: "true" } do |form| + = form_with url: spree_user_session_path, scope: :spree_user, data: { turbo: true } do |form| .row .large-12.columns#login-feedback - confirmation_result = request.query_parameters[:validation] diff --git a/app/views/layouts/_signup_tab.html.haml b/app/views/layouts/_signup_tab.html.haml index f25bf2024d9..b126a24187b 100644 --- a/app/views/layouts/_signup_tab.html.haml +++ b/app/views/layouts/_signup_tab.html.haml @@ -1,7 +1,7 @@ - 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| + = form_with model: signup_form_user, url: spree.account_path, scope: :user, data: { turbo: true } do |form| .row .large-12.columns#signup-feedback diff --git a/app/views/spree/admin/orders/_bulk_actions.html.haml b/app/views/spree/admin/orders/_bulk_actions.html.haml index 74c032ed559..6b07e372be8 100644 --- a/app/views/spree/admin/orders/_bulk_actions.html.haml +++ b/app/views/spree/admin/orders/_bulk_actions.html.haml @@ -17,7 +17,7 @@ %span.name{ "data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "send_invoice" } = t('spree.admin.orders.index.send_invoice') %div.menu_item - %span.name{ "data-controller": "bulk-actions", "data-action": "click->bulk-actions#perform", "data-bulk-actions-reflex-value": "Admin::Orders#bulk_invoice" } + %span.name{ data: { controller: "bulk-actions", action: "click->bulk-actions#printInvoices", url: spree.bulk_invoice_admin_orders_path } } = t('spree.admin.orders.index.print_invoices') %div.menu_item %span.name{ "data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "cancel_orders" } diff --git a/app/views/spree/admin/orders/_filters.html.haml b/app/views/spree/admin/orders/_filters.html.haml index fcf14a4441f..9b4b0aaa5ba 100644 --- a/app/views/spree/admin/orders/_filters.html.haml +++ b/app/views/spree/admin/orders/_filters.html.haml @@ -1,5 +1,5 @@ -%div.admin-orders-index-search{ "data-controller": "search", "data-search-restore-value": @stored_query } - = form_with url: spree.admin_orders_url, id: "orders_form", method: :get, data: { remote: true, "search-target": "form" } do +%div.admin-orders-index-search{ "data-controller": "search turbo-search", "data-search-restore-value": @stored_query } + = form_with url: spree.admin_orders_url, id: "orders_form", method: :get, data: { turbo: false, turbo_frame: 'order-results', 'turbo-search-target': 'form', "search-target": "form" } do = hidden_field_tag :page, 1, class: "page" = hidden_field_tag :per_page, 15, class: "per-page" = hidden_field_tag "[q][s]", params.dig(:q, :s) || "completed_at desc", class: "sort", "data-default": "completed_at desc" @@ -57,5 +57,4 @@ %i.icon-search = t(:filter_results) .eight.columns.omega - %button.float-left{"id": "clear_filters_button", type: "button", "data-controller": "search", "data-action": "click->search#reset" } - = t(:clear_filters) + = link_to t(:clear_filters), admin_orders_url, "id": "clear_filters_button", class: 'float-left primary button' diff --git a/app/views/spree/admin/orders/_shipment.html.haml b/app/views/spree/admin/orders/_shipment.html.haml index 599900c295c..960d4671238 100644 --- a/app/views/spree/admin/orders/_shipment.html.haml +++ b/app/views/spree/admin/orders/_shipment.html.haml @@ -10,8 +10,7 @@ = "-" %button{"class": "ship button icon-arrow-right","data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": dom_id(order, :ship)}= t(:ship) - %form - = render ShipOrderComponent.new(order: order) + = render ShipOrderComponent.new(order: order, current_page: :edit) %table.stock-contents.index %colgroup diff --git a/app/views/spree/admin/orders/_table_row.html.haml b/app/views/spree/admin/orders/_table_row.html.haml index 7cc4aae10ff..82ffb07cac4 100644 --- a/app/views/spree/admin/orders/_table_row.html.haml +++ b/app/views/spree/admin/orders/_table_row.html.haml @@ -47,9 +47,9 @@ %i.success.icon-ok-sign{"data-controller": "ephemeral"} = render AdminTooltipComponent.new(text: t('spree.admin.orders.index.edit'), link_text: "", link: edit_admin_order_path(order), link_class: "icon_link with-tip icon-edit no-text") - if order.ready_to_ship? - %form - = render ShipOrderComponent.new(order: order) - = render partial: 'admin/shared/tooltip_button', locals: {button_class: "icon-road icon_link with-tip no-text", reflex_data_id: order.id.to_s, tooltip_text: t('spree.admin.orders.index.ship'), shipment: true} + = render ShipOrderComponent.new(order: order, current_page: :index) + = render partial: 'admin/shared/tooltip_button', locals: {button_class: "icon-road icon_link with-tip no-text", data_id: order.id.to_s, tooltip_text: t('spree.admin.orders.index.ship'), shipment: true} - if order.payment_required? && order.pending_payments.reject(&:requires_authorization?).any? - = render partial: 'admin/shared/tooltip_button', locals: {button_class: "icon-capture icon_link no-text", button_reflex: "click->Admin::OrdersReflex#capture", reflex_data_id: order.id.to_s, tooltip_text: t('spree.admin.orders.index.capture')} + = link_to capture_admin_order_path(order), data: { turbo: true, turbo_method: :post } do + = render partial: 'admin/shared/tooltip_button', locals: {button_class: "icon-capture icon_link no-text", data_id: order.id.to_s, tooltip_text: t('spree.admin.orders.index.capture')} diff --git a/app/views/spree/admin/orders/bulk_invoice.turbo_stream.haml b/app/views/spree/admin/orders/bulk_invoice.turbo_stream.haml new file mode 100644 index 00000000000..49e4173495f --- /dev/null +++ b/app/views/spree/admin/orders/bulk_invoice.turbo_stream.haml @@ -0,0 +1,2 @@ += turbo_stream.append 'orders-index' do + = render partial: "spree/admin/orders/bulk/invoice_modal", locals: { invoice_url: "/admin/orders/invoices/#{@file_id}" } diff --git a/app/views/spree/admin/orders/cancel_orders.turbo_stream.haml b/app/views/spree/admin/orders/cancel_orders.turbo_stream.haml new file mode 100644 index 00000000000..297ff64831a --- /dev/null +++ b/app/views/spree/admin/orders/cancel_orders.turbo_stream.haml @@ -0,0 +1,3 @@ +- @cancelled_orders.each do |order| + = turbo_stream.replace dom_id(order) do + = render partial: "spree/admin/orders/table_row", locals: { order: } diff --git a/app/views/spree/admin/orders/capture.turbo_stream.haml b/app/views/spree/admin/orders/capture.turbo_stream.haml new file mode 100644 index 00000000000..a6b39c41e4e --- /dev/null +++ b/app/views/spree/admin/orders/capture.turbo_stream.haml @@ -0,0 +1,6 @@ +- if @saved + = turbo_stream.replace dom_id(@order) do + = render partial: "spree/admin/orders/table_row", locals: { order: @order.reload, success: true } +- else + = turbo_stream.append "flashes" do + = render(partial: 'admin/shared/flashes', locals: { flashes: flash }) \ No newline at end of file diff --git a/app/views/spree/admin/orders/index.html.haml b/app/views/spree/admin/orders/index.html.haml index 26a7965a698..fb4a53b2b30 100644 --- a/app/views/spree/admin/orders/index.html.haml +++ b/app/views/spree/admin/orders/index.html.haml @@ -15,19 +15,22 @@ - content_for :table_filter do = render partial: 'filters' -#orders-index{"data-controller": "search checked"} - = render partial: "table", locals: { pagy: @pagy, orders: @orders } +%turbo-frame#order-results + #orders-index{"data-controller": "search checked"} + = render partial: "table", locals: { pagy: @pagy, orders: @orders } = render 'spree/admin/shared/custom-confirm' -= render ConfirmModalComponent.new(id: "resend_confirmation", confirm_actions: "click->bulk-actions#perform", controller: "bulk-actions", reflex: "Admin::Orders#resend_confirmation_emails") do += render ConfirmModalComponent.new(id: "resend_confirmation", form_url: resend_confirmation_emails_admin_orders_path) do .margin-bottom-30 = t('.resend_confirmation_confirm_html') -= render ConfirmModalComponent.new(id: "send_invoice", confirm_actions: "click->bulk-actions#perform", controller: "bulk-actions", reflex: "Admin::Orders#send_invoices") do += render ConfirmModalComponent.new(id: "send_invoice", form_url: send_invoices_admin_orders_path) do .margin-bottom-30 = t('.send_invoice_confirm_html') -= render ConfirmModalComponent.new(id: "cancel_orders", confirm_actions: "click->bulk-actions#perform", controller: "bulk-actions", reflex: "Admin::Orders#cancel_orders", message: "spree/admin/orders/messages/cancel_orders") do += render ConfirmModalComponent.new(id: "cancel_orders", form_url: cancel_orders_admin_orders_path) do .margin-bottom-30 = t("js.admin.orders.cancel_the_order_html") + .margin-bottom-30 + = render partial: "spree/admin/orders/messages/cancel_orders" diff --git a/app/views/spree/admin/orders/messages/_cancel_orders.html.haml b/app/views/spree/admin/orders/messages/_cancel_orders.html.haml index 9b689ff75ea..78690d2d2af 100644 --- a/app/views/spree/admin/orders/messages/_cancel_orders.html.haml +++ b/app/views/spree/admin/orders/messages/_cancel_orders.html.haml @@ -1,11 +1,10 @@ .modal-message - %form{ "data-bulk-actions-target": "extraParams" } - %input{ type: "checkbox", name: "send_cancellation_email", value: "1", id: "send_cancellation_email", checked: "true" } - %label{ for: "send_cancellation_email" } - = t("js.admin.orders.cancel_the_order_send_cancelation_email") - %br - %input{ type: "checkbox", name: "restock_items", value: "1", id: "restock_items", checked: "true" } - %label{ for: "restock_items" } - = t("js.admin.orders.restock_items") - .margin-bottom-30 + %input{ type: "checkbox", name: "send_cancellation_email", value: "1", id: "send_cancellation_email", checked: "true" } + %label{ for: "send_cancellation_email" } + = t("js.admin.orders.cancel_the_order_send_cancelation_email") + %br + %input{ type: "checkbox", name: "restock_items", value: "1", id: "restock_items", checked: "true" } + %label{ for: "restock_items" } + = t("js.admin.orders.restock_items") + .margin-bottom-30 diff --git a/app/views/spree/admin/orders/resend_confirmation_emails.turbo_stream.haml b/app/views/spree/admin/orders/resend_confirmation_emails.turbo_stream.haml new file mode 100644 index 00000000000..bbf577bbeeb --- /dev/null +++ b/app/views/spree/admin/orders/resend_confirmation_emails.turbo_stream.haml @@ -0,0 +1,2 @@ += turbo_stream.append "flashes" do + = render partial: 'admin/shared/flashes', locals: { flashes: flash } \ No newline at end of file diff --git a/app/views/spree/admin/orders/send_invoices.turbo_stream.haml b/app/views/spree/admin/orders/send_invoices.turbo_stream.haml new file mode 100644 index 00000000000..bbf577bbeeb --- /dev/null +++ b/app/views/spree/admin/orders/send_invoices.turbo_stream.haml @@ -0,0 +1,2 @@ += turbo_stream.append "flashes" do + = render partial: 'admin/shared/flashes', locals: { flashes: flash } \ No newline at end of file diff --git a/app/views/spree/admin/orders/ship.turbo_stream.haml b/app/views/spree/admin/orders/ship.turbo_stream.haml new file mode 100644 index 00000000000..a942eabf1c5 --- /dev/null +++ b/app/views/spree/admin/orders/ship.turbo_stream.haml @@ -0,0 +1,6 @@ +- if @shipped + = turbo_stream.replace dom_id(@order) do + = render partial: "spree/admin/orders/table_row", locals: { order: @order.reload, success: true } +- else + = turbo_stream.append "flashes" do + = render(partial: 'admin/shared/flashes', locals: { flashes: flash }) \ No newline at end of file diff --git a/app/views/spree/admin/shared/_order_links.html.haml b/app/views/spree/admin/shared/_order_links.html.haml index a79cf3b2f51..690fcaca008 100644 --- a/app/views/spree/admin/shared/_order_links.html.haml +++ b/app/views/spree/admin/shared/_order_links.html.haml @@ -13,12 +13,11 @@ %i{ class: link[:icon] } %span=link[:name] - else - %a.menu_item{ href: link[:url], target: link[:target] || "_self", data: { method: link[:method], "ujs-navigate": link[:method] ? "false" : "undefined", confirm: link[:confirm] } } + = link_to link[:url], class: 'menu_item', target: link[:target] || "_self", data: { turbo: true, turbo_method: link[:method], turbo_confirm: link[:confirm], action: 'dropdown#submitLink' } do %span %i{ class: link[:icon] } %span=link[:name] = render 'spree/admin/shared/custom-confirm' - if order_shipment_ready?(@order) - %form - = render ShipOrderComponent.new(order: @order) + = render ShipOrderComponent.new(order: @order, current_page: :edit) diff --git a/app/views/spree/user_confirmations/create.turbo_stream.haml b/app/views/spree/user_confirmations/create.turbo_stream.haml new file mode 100644 index 00000000000..9c40b050c5e --- /dev/null +++ b/app/views/spree/user_confirmations/create.turbo_stream.haml @@ -0,0 +1,2 @@ += turbo_stream.update "#{params[:tab] || 'forgot'}-feedback" do + = render partial: 'shared/flashes', locals: { flashes: flash } diff --git a/app/views/spree/user_sessions/create.turbo_stream.haml b/app/views/spree/user_sessions/create.turbo_stream.haml new file mode 100644 index 00000000000..2621c4fb5d9 --- /dev/null +++ b/app/views/spree/user_sessions/create.turbo_stream.haml @@ -0,0 +1,2 @@ += turbo_stream.update 'login-feedback' do + = render partial: 'layouts/alert', locals: { message: @message, type: 'alert' } \ No newline at end of file diff --git a/app/views/spree/users/create.turbo_stream.haml b/app/views/spree/users/create.turbo_stream.haml new file mode 100644 index 00000000000..86fdd86ea68 --- /dev/null +++ b/app/views/spree/users/create.turbo_stream.haml @@ -0,0 +1,2 @@ += turbo_stream.update 'signup-tab' do + = render partial: 'layouts/signup_tab', locals: { signup_form_user: @user } diff --git a/app/views/spree/users/registered_email.turbo_stream.haml b/app/views/spree/users/registered_email.turbo_stream.haml new file mode 100644 index 00000000000..e6ef58711c1 --- /dev/null +++ b/app/views/spree/users/registered_email.turbo_stream.haml @@ -0,0 +1,2 @@ += turbo_stream.replace 'login-feedback' do + = render partial: "layouts/alert", locals: { type: "alert", message: t('devise.failure.already_registered') } diff --git a/app/views/spree/users/render_alert_timestamp_error_message.turbo_stream.haml b/app/views/spree/users/render_alert_timestamp_error_message.turbo_stream.haml new file mode 100644 index 00000000000..99bb2e4fcab --- /dev/null +++ b/app/views/spree/users/render_alert_timestamp_error_message.turbo_stream.haml @@ -0,0 +1,3 @@ += turbo_stream.update 'signup-feedback' do + = render partial: 'layouts/alert', + locals: { type: "alert", message: InvisibleCaptcha.timestamp_error_message } diff --git a/app/views/user_passwords/create.turbo_stream.haml b/app/views/user_passwords/create.turbo_stream.haml new file mode 100644 index 00000000000..97833070189 --- /dev/null +++ b/app/views/user_passwords/create.turbo_stream.haml @@ -0,0 +1,2 @@ += turbo_stream.update 'forgot-feedback' do + = render partial: 'layouts/alert', locals: { type: @type, message: @message, tab: @tab, unconfirmed: @unconfirmed, email: params.dig(:spree_user, :email) } \ No newline at end of file diff --git a/app/views/voucher_adjustments/render_error.turbo_stream.haml b/app/views/voucher_adjustments/render_error.turbo_stream.haml new file mode 100644 index 00000000000..9c5e68bef0b --- /dev/null +++ b/app/views/voucher_adjustments/render_error.turbo_stream.haml @@ -0,0 +1,4 @@ += turbo_stream.replace 'flashes' do + = render partial: "shared/flashes", locals: { flashes: flash } += turbo_stream.replace 'voucher-section' do + = render partial: 'checkout/voucher_section', locals: { order: @order, voucher_adjustment: @order.voucher_adjustments.first } \ No newline at end of file diff --git a/app/views/voucher_adjustments/update_payment_section.turbo_stream.haml b/app/views/voucher_adjustments/update_payment_section.turbo_stream.haml new file mode 100644 index 00000000000..d56c1bfd104 --- /dev/null +++ b/app/views/voucher_adjustments/update_payment_section.turbo_stream.haml @@ -0,0 +1,2 @@ += turbo_stream.replace 'checkout-payment-methods' do + = render partial: "checkout/payment", locals: { step: "payment" } \ No newline at end of file diff --git a/app/webpacker/channels/session_channel.js b/app/webpacker/channels/session_channel.js index d43acef4627..fd5e716fe85 100644 --- a/app/webpacker/channels/session_channel.js +++ b/app/webpacker/channels/session_channel.js @@ -1,8 +1,9 @@ import consumer from './consumer' -import CableReady from 'cable_ready' consumer.subscriptions.create("SessionChannel", { received(data) { - if (data.cableReady) CableReady.perform(data.operations) + if (!data.selector) return; + + document.querySelector(data.selector).innerHTML = data.html; } }); diff --git a/app/webpacker/controllers/bulk_actions_controller.js b/app/webpacker/controllers/bulk_actions_controller.js index 9de40fe8cb3..bc9f3f5fb45 100644 --- a/app/webpacker/controllers/bulk_actions_controller.js +++ b/app/webpacker/controllers/bulk_actions_controller.js @@ -1,24 +1,43 @@ -import ApplicationController from "./application_controller"; - -export default class extends ApplicationController { - static targets = ["extraParams"] - static values = { reflex: String } +import { Controller } from "stimulus"; +export default class extends Controller { connect() { - super.connect(); + this.element.addEventListener('turbo:submit-end', this.closeModal.bind(this)); + document.addEventListener('modal-open', this.modalOpen.bind(this)); + document.addEventListener('modal-close', this.modalClose.bind(this)); } - perform() { - let params = { bulk_ids: this.getSelectedIds() }; + disconnect() { + this.element.removeEventListener('turbo:submit-end', this.closeModal); + document.removeEventListener('modal-open', this.modalOpen); + document.removeEventListener('modal-close', this.modalClose); + } - if (this.hasExtraParamsTarget) { - Object.assign(params, this.extraFormData()) - } + printInvoices(e) { + const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content'); + const data = { bulk_ids: this.getSelectedIds() }; - this.stimulate(this.reflexValue, params); + fetch(e.target.getAttribute('data-url'), { + method: "POST", + headers: { + Accept: "text/vnd.turbo-stream.html", + "Content-Type": "application/json", + "X-CSRF-Token": csrfToken + }, + body: JSON.stringify(data), + }) + .then((response) => response.text()) + .then((html) => { + Turbo.renderStreamMessage(html); + }) + .catch((error) => console.error(error)); } - // private + closeModal(event) { + if (event.detail.success) { + this.element.querySelector("button[type='button']").click(); + } + } getSelectedIds() { const checkboxes = document.querySelectorAll( @@ -27,9 +46,26 @@ export default class extends ApplicationController { return Array.from(checkboxes).map((checkbox) => checkbox.value); } - extraFormData() { - if (this.extraParamsTarget.constructor.name !== "HTMLFormElement") { return {} } + modalOpen(e) { + if (!e.target.contains(this.element)) return; + + this.getSelectedIds().forEach((value) => { + const input = Object.assign(document.createElement("input"), { + type: "hidden", + name: "bulk_ids[]", + value, + }); + this.element.appendChild(input); + }); + + this.element.querySelectorAll("input[type='checkbox']").forEach(element => { + element.checked = true; + }); + } + + modalClose(e) { + if (!e.target.contains(this.element)) return; - return Object.fromEntries(new FormData(this.extraParamsTarget).entries()) + this.element.querySelectorAll("input[name='bulk_ids[]']").forEach(ele => ele.remove()); } } diff --git a/app/webpacker/controllers/dropdown_controller.js b/app/webpacker/controllers/dropdown_controller.js index 701fdf27a78..27dbac33293 100644 --- a/app/webpacker/controllers/dropdown_controller.js +++ b/app/webpacker/controllers/dropdown_controller.js @@ -18,6 +18,22 @@ export default class extends Controller { this.#stopPropagation(event); } + submitLink(event) { + const link = event.currentTarget; + const method = link.getAttribute('data-turbo-method'); + const confirmMessage = link.getAttribute('data-turbo-confirm'); + + if (link && confirmMessage && [null, 'get'].includes(method)) { + // Manualy visit link + event.preventDefault(); + if (confirm(link.getAttribute('data-turbo-confirm'))) { + Turbo.visit(link.href); + } + } + + this.closeOnMenu(event); + } + // private #close(event) { diff --git a/app/webpacker/controllers/index.js b/app/webpacker/controllers/index.js index 55c57a46cef..147ceac1723 100644 --- a/app/webpacker/controllers/index.js +++ b/app/webpacker/controllers/index.js @@ -5,7 +5,6 @@ import { definitionsFromContext } from "stimulus/webpack-helpers"; import StimulusReflex from "stimulus_reflex"; import consumer from "../channels/consumer"; import controller from "../controllers/application_controller"; -import CableReady from "cable_ready"; import RailsNestedForm from '@stimulus-components/rails-nested-form/dist/stimulus-rails-nested-form.umd.js' // the default module entry point is broken const application = Application.start(); diff --git a/app/webpacker/controllers/login_modal_controller.js b/app/webpacker/controllers/login_modal_controller.js index 7fc2111ab1c..755809b483f 100644 --- a/app/webpacker/controllers/login_modal_controller.js +++ b/app/webpacker/controllers/login_modal_controller.js @@ -75,8 +75,8 @@ export default class extends Controller { }), headers: { "Content-type": "application/json; charset=UTF-8" }, }) - .then((data) => data.json()) - .then(CableReady.perform); + .then(response => response.text()) + .then(html => { Turbo.renderStreamMessage(html) }); } returnHome() { diff --git a/app/webpacker/controllers/mixins/useOpenAndCloseAsAModal.js b/app/webpacker/controllers/mixins/useOpenAndCloseAsAModal.js index 7648563791c..bfdfddcb22f 100644 --- a/app/webpacker/controllers/mixins/useOpenAndCloseAsAModal.js +++ b/app/webpacker/controllers/mixins/useOpenAndCloseAsAModal.js @@ -3,11 +3,13 @@ export const useOpenAndCloseAsAModal = (controller) => { open: function () { this.backgroundTarget.style.display = "block"; this.modalTarget.style.display = "block"; + let modalOpen = new Event('modal-open', { bubbles: true }); setTimeout(() => { this.modalTarget.classList.add("in"); this.backgroundTarget.classList.add("in"); document.querySelector("body").classList.add("modal-open"); + this.element.dispatchEvent(modalOpen); }); }.bind(controller), @@ -17,11 +19,13 @@ export const useOpenAndCloseAsAModal = (controller) => { this.modalTarget.classList.remove("in"); this.backgroundTarget.classList.remove("in"); + let modalClose = new Event('modal-close', { bubbles: true }); document.querySelector("body").classList.remove("modal-open"); setTimeout(() => { this.backgroundTarget.style.display = "none"; this.modalTarget.style.display = "none"; + this.element.dispatchEvent(modalClose) if (remove) { this.element.remove() } }, 200); }.bind(controller), diff --git a/app/webpacker/controllers/primary_details_controller.js b/app/webpacker/controllers/primary_details_controller.js index b4e72be4cf6..eb73480bc6a 100644 --- a/app/webpacker/controllers/primary_details_controller.js +++ b/app/webpacker/controllers/primary_details_controller.js @@ -22,13 +22,13 @@ export default class extends Controller { `?stimulus=true&enterprise_sells=${this.enterpriseSellsValue}&is_primary_producer=${this.primaryProducerValue}`, { method: "GET", - headers: { "Content-type": "application/json; charset=UTF-8" }, + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + 'Accept': 'text/vnd.turbo-stream.html' + } } ) - .then((data) => data.json()) - .then((operation) => { - CableReady.perform(operation); - this.spinnerTarget.classList.add("hidden"); - }); - } + .then(response => response.text()) + .then(html => Turbo.renderStreamMessage(html)) + .then(() => this.spinnerTarget.classList.add("hidden"))} } diff --git a/app/webpacker/controllers/scoped_channel_controller.js b/app/webpacker/controllers/scoped_channel_controller.js index eb777da09f7..974dba1a3dc 100644 --- a/app/webpacker/controllers/scoped_channel_controller.js +++ b/app/webpacker/controllers/scoped_channel_controller.js @@ -1,6 +1,5 @@ import { Controller } from "stimulus"; import consumer from "../channels/consumer"; -import CableReady from "cable_ready"; export default class extends Controller { static values = { id: String }; @@ -10,7 +9,9 @@ export default class extends Controller { { channel: "ScopedChannel", id: this.idValue }, { received(data) { - if (data.cableReady) CableReady.perform(data.operations); + if (!data.selector) return; + + document.querySelector(data.selector).innerHTML = data.html; }, } ); diff --git a/app/webpacker/controllers/turbo_search_controller.js b/app/webpacker/controllers/turbo_search_controller.js new file mode 100644 index 00000000000..5561a0659fb --- /dev/null +++ b/app/webpacker/controllers/turbo_search_controller.js @@ -0,0 +1,25 @@ +import { Controller } from "stimulus"; + +export default class extends Controller { + static targets = ['form']; + + connect() { + this.formTarget.addEventListener('submit', this.turboSubmit); + } + + disconnect() { + this.formTarget.removeEventListener('submit', this.turboSubmit); + } + + turboSubmit(e) { + e.preventDefault(); + const form = e.target; + const url = new URL(form.action); + const formData = new FormData(form); + const params = new URLSearchParams(formData).toString(); + + // Manually visit the new URL with the search params + // inorder to preserve params + Turbo.visit(`${url.pathname}?${params}`, { action: 'replace' }); + } +} diff --git a/app/webpacker/js/mrujs.js b/app/webpacker/js/mrujs.js deleted file mode 100644 index 602fc56ca4a..00000000000 --- a/app/webpacker/js/mrujs.js +++ /dev/null @@ -1,28 +0,0 @@ -import CableReady from "cable_ready"; -import mrujs from "mrujs"; -import { CableCar } from "mrujs/plugins"; - -mrujs.start({ - plugins: [new CableCar(CableReady, { mimeType: "text/vnd.cable-ready.json" })], -}); - -// Handle legacy jquery ujs buttons -document.addEventListener("ajax:beforeNavigation", (event) => { - if (event.detail.element.dataset.ujsNavigate !== "false") return; - - event.preventDefault(); - - if (event.detail.fetchResponse.response.redirected) { - document.location.href = event.detail.fetchResponse.response.url; - } -}); - -document.addEventListener("ajax:beforeSend", (event) => { - window.Turbo.navigator.adapter.progressBar.setValue(0); - window.Turbo.navigator.adapter.progressBar.show(); -}); - -document.addEventListener("ajax:complete", (event) => { - window.Turbo.navigator.adapter.progressBar.setValue(100); - window.Turbo.navigator.adapter.progressBar.hide(); -}); diff --git a/app/webpacker/js/ujs.js b/app/webpacker/js/ujs.js new file mode 100644 index 00000000000..9d2004191f3 --- /dev/null +++ b/app/webpacker/js/ujs.js @@ -0,0 +1,2 @@ +import Rails from "@rails/ujs"; +Rails.start(); diff --git a/app/webpacker/packs/admin.js b/app/webpacker/packs/admin.js index 1625ce11bb5..3c217211181 100644 --- a/app/webpacker/packs/admin.js +++ b/app/webpacker/packs/admin.js @@ -2,7 +2,7 @@ import "controllers"; import "channels"; import "../js/turbo"; import "../js/hotkeys"; -import "../js/mrujs"; +import "../js/ujs"; import "../js/matomo"; import "../js/moment"; diff --git a/app/webpacker/packs/application.js b/app/webpacker/packs/application.js index f8263d1830a..f570fc23b55 100644 --- a/app/webpacker/packs/application.js +++ b/app/webpacker/packs/application.js @@ -1,7 +1,7 @@ import "controllers"; import "../js/turbo"; import "../js/hotkeys"; -import "../js/mrujs"; +import "../js/ujs"; import "../js/matomo"; import "../js/moment"; diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 8036788d462..0b41958e842 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -33,6 +33,7 @@ member do get :welcome patch :register + patch :remove_logo end resources :connected_apps, only: [:create, :destroy] diff --git a/config/routes/spree.rb b/config/routes/spree.rb index c6c84fddbec..cb3ee2506ad 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -155,5 +155,21 @@ resources :payment_methods end + namespace :admin do + resources :orders do + member do + post :capture + post :ship + end + + collection do + post :bulk_invoice + post :cancel_orders + post :resend_confirmation_emails + post :send_invoices + end + end + end + resources :products end diff --git a/package.json b/package.json index b515b4bb026..3cb7496f98e 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,9 @@ "@floating-ui/dom": "^1.6.11", "@hotwired/stimulus": "^3.2", "@hotwired/turbo": "^8.0.12", + "@rails/ujs": "^7.1.3-4", "@rails/webpacker": "5.4.4", "@stimulus-components/rails-nested-form": "^5.0.0", - "cable_ready": "5.0.5", "flatpickr": "^4.6.9", "foundation-sites": "^5.5.3", "hotkeys-js": "^3.13.7", @@ -25,7 +25,6 @@ "leaflet-geosearch": "4.0.0", "leaflet-providers": "2.0.0", "moment": "^2.30.1", - "mrujs": "^1.0.2", "select2": "^4.0.13", "shortcut-buttons-flatpickr": "^0.4.0", "stimulus": "^3.2.2", diff --git a/spec/controllers/spree/user_sessions_controller_spec.rb b/spec/controllers/spree/user_sessions_controller_spec.rb index 5f035752637..25dbdb37c79 100644 --- a/spec/controllers/spree/user_sessions_controller_spec.rb +++ b/spec/controllers/spree/user_sessions_controller_spec.rb @@ -14,8 +14,7 @@ context "when referer is not '/checkout'" do it "redirects to root" do spree_post :create, spree_user: { email: user.email, password: user.password } - - expect(response).to have_http_status(:ok) + expect(response).to have_http_status(:found) expect(response.body).to match(root_path).and match("redirect") end end @@ -26,7 +25,7 @@ it "redirects to checkout" do spree_post :create, spree_user: { email: user.email, password: user.password } - expect(response).to have_http_status(:ok) + expect(response).to have_http_status(:found) expect(response.body).to match(checkout_path).and match("redirect") end end @@ -36,7 +35,8 @@ render_views it "returns an error" do - spree_post :create, spree_user: { email: user.email, password: "wrong" } + spree_post :create, spree_user: { email: user.email, password: "wrong" }, + format: :turbo_stream expect(response).to have_http_status(:unauthorized) expect(response.body).to include "Invalid email or password" diff --git a/spec/controllers/user_passwords_controller_spec.rb b/spec/controllers/user_passwords_controller_spec.rb index 594f5dbd79d..a7796e37f40 100644 --- a/spec/controllers/user_passwords_controller_spec.rb +++ b/spec/controllers/user_passwords_controller_spec.rb @@ -14,20 +14,20 @@ describe "create" do it "returns 404 if user is not found" do - spree_post :create, spree_user: { email: "xxxxxxxxxx@example.com" } + spree_post :create, spree_user: { email: "xxxxxxxxxx@example.com" }, format: :turbo_stream expect(response.status).to eq 404 expect(response.body).to match 'Email address not found' end it "returns 422 if user is registered but not confirmed" do - spree_post :create, spree_user: { email: unconfirmed_user.email } + spree_post :create, spree_user: { email: unconfirmed_user.email }, format: :turbo_stream expect(response.status).to eq 422 expect(response.body).to match "You must confirm your email \ address before you can reset your password." end it "returns 200 when password reset was successful" do - spree_post :create, spree_user: { email: user.email } + spree_post :create, spree_user: { email: user.email }, format: :turbo_stream expect(response.status).to eq 200 expect(response.body).to match "An email with instructions on resetting \ your password has been sent!" diff --git a/spec/jobs/report_job_spec.rb b/spec/jobs/report_job_spec.rb index fd8c9aaf86e..b3f91240063 100644 --- a/spec/jobs/report_job_spec.rb +++ b/spec/jobs/report_job_spec.rb @@ -3,8 +3,6 @@ require 'spec_helper' RSpec.describe ReportJob do - include CableReady::Broadcaster - let(:report_args) { { report_class:, user:, params:, format:, blob: } } @@ -41,8 +39,10 @@ with_channel = report_args.merge(channel:) ReportJob.perform_later(**with_channel) - - expect(cable_ready[channel]).to receive(:broadcast).and_call_original + expect(ActionCable.server).to receive(:broadcast).with( + channel, + instance_of(Hash) + ).twice.and_call_original expect { perform_enqueued_jobs(only: ReportJob) diff --git a/spec/system/admin/enterprises_spec.rb b/spec/system/admin/enterprises_spec.rb index 86d007a46b5..365c4698f8e 100644 --- a/spec/system/admin/enterprises_spec.rb +++ b/spec/system/admin/enterprises_spec.rb @@ -685,7 +685,7 @@ expect(distributor1.white_label_logo).to be_attached click_button "Remove" within ".reveal-modal" do - click_button "Confirm" + click_link "Confirm" end expect(flash_message).to match(/Logo removed/) distributor1.reload diff --git a/spec/system/admin/order_spec.rb b/spec/system/admin/order_spec.rb index 61a056e9c72..974fc0cd724 100644 --- a/spec/system/admin/order_spec.rb +++ b/spec/system/admin/order_spec.rb @@ -951,10 +951,12 @@ def new_order_with_distribution(distributor, order_cycle) order.finalize! # ensure order has a payment to capture order.payments << create(:check_payment, order:, amount: order.total) order.payments.first.capture! + login_as_admin visit spree.edit_admin_order_path(order) end it "ships the order and shipment email is sent" do + login_as_admin expect(order.reload.shipped?).to be false click_button 'Ship' @@ -972,6 +974,7 @@ def new_order_with_distribution(distributor, order_cycle) end it "ships the order without sending email" do + login_as_admin expect(order.reload.shipped?).to be false click_button 'Ship' diff --git a/yarn.lock b/yarn.lock index b52c301a6e0..e28e50d1560 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1394,6 +1394,11 @@ resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-7.0.4.tgz#70a3ca56809f7aaabb80af2f9c01ae51e1a8ed41" integrity sha512-tz4oM+Zn9CYsvtyicsa/AwzKZKL+ITHWkhiu7x+xF77clh2b4Rm+s6xnOgY/sGDWoFWZmtKsE95hxBPkgQQNnQ== +"@rails/ujs@^7.1.3-4": + version "7.1.3-4" + resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-7.1.3-4.tgz#1dddea99d5c042e8513973ea709b2cb7e840dc2d" + integrity sha512-z0ckI5jrAJfImcObjMT1RBz2IxH6I5q6ZTMFex6AfxSQKZuuL8JxAXvg2CvBuodGCxKvybFVolDyMHXlBLeYAA== + "@rails/webpacker@5.4.4": version "5.4.4" resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-5.4.4.tgz#971a41b987c096c908ce4088accd57c1a9a7e2f7" @@ -2433,7 +2438,7 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -cable_ready@5.0.5, cable_ready@^5.0.5: +cable_ready@^5.0.5: version "5.0.5" resolved "https://registry.yarnpkg.com/cable_ready/-/cable_ready-5.0.5.tgz#45dd12ae5b3c5c53a1b42c10785e79ff87a5be22" integrity sha512-qPC6zaI8h59BzMH3MxtpuMC+H33VJTA2eVddL6fZSWz01jJ2Y3okld01oYWQoKwE2yle/tvHbyuhoKxD4mhEuw== @@ -6193,7 +6198,7 @@ moment@^2.30.1: resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== -morphdom@2.6.1, "morphdom@>=2.6.0 <3.0.0": +morphdom@2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/morphdom/-/morphdom-2.6.1.tgz#e868e24f989fa3183004b159aed643e628b4306e" integrity sha512-Y8YRbAEP3eKykroIBWrjcfMw7mmwJfjhqdpSvoqinu8Y702nAwikpXcNFDiIkyvfCLxLM9Wu95RZqo4a9jFBaA== @@ -6215,13 +6220,6 @@ mri@^1.2.0: resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== -mrujs@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/mrujs/-/mrujs-1.0.2.tgz#f19818735d8f5865dab75254f4cfc38d33804f2e" - integrity sha512-dGTUHLH+COsGOn78R7lUFUK/eDLaY8W14N25EymB6lXknENeyoVL31Hsxfb2hEsMb2yjBx0cB//ibO/NTECIzQ== - dependencies: - morphdom ">=2.6.0 <3.0.0" - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"