diff --git a/app/assets/javascripts/templates/out_of_stock.html.haml b/app/assets/javascripts/templates/out_of_stock.html.haml index 9aeeae106e..09ed5f3001 100644 --- a/app/assets/javascripts/templates/out_of_stock.html.haml +++ b/app/assets/javascripts/templates/out_of_stock.html.haml @@ -1,4 +1,4 @@ -// please update the modal html in app/view/checkout/edit.html.haml if updating this template +// please update the modal html in app/components/out_of_stock_modal_component/out_of_stock_modal_component.html.haml if updating this template %a.close-reveal-modal{"ng-click" => "$close()"} %i.ofn-i_009-close diff --git a/app/components/out_of_stock_modal_component.rb b/app/components/out_of_stock_modal_component.rb new file mode 100644 index 0000000000..f22c26f760 --- /dev/null +++ b/app/components/out_of_stock_modal_component.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class OutOfStockModalComponent < ModalComponent + def initialize(id:, variants: [], redirect: false) + super(id:, modal_class: "medium", instant: true) + + @variants = variants + @redirect = redirect + end +end diff --git a/app/components/out_of_stock_modal_component/out_of_stock_modal_component.html.haml b/app/components/out_of_stock_modal_component/out_of_stock_modal_component.html.haml new file mode 100644 index 0000000000..dd71cbf7f7 --- /dev/null +++ b/app/components/out_of_stock_modal_component/out_of_stock_modal_component.html.haml @@ -0,0 +1,26 @@ +%div{ id: @id, "data-controller": "modal out-of-stock-modal", "data-action": "keyup@document->modal#closeIfEscapeKey modal:closing->out-of-stock-modal#redirect", "data-modal-instant-value": @instant, "data-out-of-stock-modal-redirect-value": @redirect } + .reveal-modal-bg.fade{ "data-modal-target": "background", "data-action": "click->modal#close" } + .reveal-modal.fade.modal-component{ "data-modal-target": "modal", class: @modal_class } + - # please update app/assets/javascripts/templates/out_of_stock.html.haml if updating this view + %a.close-reveal-modal{"data-action": "click->modal#close" } + %i.ofn-i_009-close + %h3 + = t("js.out_of_stock.reduced_stock_available") + %p + = t("js.out_of_stock.out_of_stock_text") + - @variants.each do |variant| + - if variant.on_hand == 0 + %p + %em + = "#{variant.name_to_display} - #{variant.unit_to_display}" + %span + = t("js.out_of_stock.now_out_of_stock") + - if variant.on_hand > 0 + %p + %em + = "#{variant.name_to_display} - #{variant.unit_to_display}" + %span + = t("js.out_of_stock.only_n_remaining", num: variant.on_hand) + + .text-center + %input{ class: "button icon-plus #{close_button_class}", type: 'button', value: t('js.admin.modals.close'), "data-action": "click->modal#close" } diff --git a/app/webpacker/controllers/out_of_stock_modal_controller.js b/app/webpacker/controllers/out_of_stock_modal_controller.js new file mode 100644 index 0000000000..2187aca37b --- /dev/null +++ b/app/webpacker/controllers/out_of_stock_modal_controller.js @@ -0,0 +1,19 @@ +import { Controller } from "stimulus"; + +// This is meant to be used with the "modal:closing" event, ie: +// +//
+//
+// +export default class extends Controller { + static values = { redirect: { type: Boolean, default: false } }; + + redirect() { + if (this.redirectValue) { + window.location.pathname = "/shop"; + } + } +} diff --git a/spec/javascripts/stimulus/out_of_stock_modal_controller_test.js b/spec/javascripts/stimulus/out_of_stock_modal_controller_test.js new file mode 100644 index 0000000000..7eaf316e4b --- /dev/null +++ b/spec/javascripts/stimulus/out_of_stock_modal_controller_test.js @@ -0,0 +1,72 @@ +/** + * @jest-environment jsdom + */ + +import { Application } from "stimulus"; +import out_of_stock_modal_controller from "../../../app/webpacker/controllers/out_of_stock_modal_controller"; + +describe("OutOfStockModalController", () => { + beforeAll(() => { + const application = Application.start(); + application.register("out-of-stock-modal", out_of_stock_modal_controller); + }); + + let originalWindowLocation = window.location; + + beforeEach(() => { + Object.defineProperty(window, "location", { + configurable: true, + enumerable: true, + value: new URL(window.location.href), + }); + }); + + afterEach(() => { + Object.defineProperty(window, "location", { + configurable: true, + enumerable: true, + value: originalWindowLocation, + }); + }); + + // We use window to dispatch the closing event so we don't need to set up another controller + describe("#redirect", () => { + describe("when redirect value is false", () => { + beforeEach(() => { + document.body.innerHTML = ` +
+
+ `; + }); + + it("does not redirect", () => { + const event = new Event("closing"); + window.dispatchEvent(event); + + expect(window.location.href).not.toBe("/shop"); + }); + }); + + describe("when redirect value is true", () => { + beforeEach(() => { + document.body.innerHTML = ` +
+
+ `; + }); + + it("redirects to /shop", () => { + const event = new Event("closing"); + window.dispatchEvent(event); + + expect(window.location.pathname).toBe("/shop"); + }); + }); + }); +});