From 0a04342712667d8950e290f4261dd5c3d63662cb Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Wed, 21 Aug 2024 15:50:39 +1000 Subject: [PATCH 01/26] Remove event listener on disconnect It prevents memory leak --- app/components/vertical_ellipsis_menu/component_controller.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/components/vertical_ellipsis_menu/component_controller.js b/app/components/vertical_ellipsis_menu/component_controller.js index c783adf170c..97b13592bd3 100644 --- a/app/components/vertical_ellipsis_menu/component_controller.js +++ b/app/components/vertical_ellipsis_menu/component_controller.js @@ -8,6 +8,10 @@ export default class extends Controller { window.addEventListener("click", this.#hideIfClickedOutside); } + disconnect() { + window.removeEventListener("click", this.#hideIfClickedOutside); + } + toggle() { this.contentTarget.classList.toggle("show"); } From 4756ab47c21b2e6c733f0f138e7c21787241e9b3 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Wed, 21 Aug 2024 16:02:05 +1000 Subject: [PATCH 02/26] Wire preview link via turbo-stream --- .../admin/product_preview_controller.rb | 15 +++++++++++++++ .../admin/products_v3/_product_row.html.haml | 1 + .../products_v3/product_preview.turbo_stream.haml | 5 +++++ config/routes/admin.rb | 2 ++ 4 files changed, 23 insertions(+) create mode 100644 app/controllers/admin/product_preview_controller.rb create mode 100644 app/views/admin/products_v3/product_preview.turbo_stream.haml diff --git a/app/controllers/admin/product_preview_controller.rb b/app/controllers/admin/product_preview_controller.rb new file mode 100644 index 00000000000..27e21a27844 --- /dev/null +++ b/app/controllers/admin/product_preview_controller.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Admin + class ProductPreviewController < Spree::Admin::BaseController + def show + @id = params[:id] + # TODO load product data based on param + respond_with do |format| + format.turbo_stream { + render "admin/products_v3/product_preview", status: :ok, locals: { id: @id } + } + end + end + end +end diff --git a/app/views/admin/products_v3/_product_row.html.haml b/app/views/admin/products_v3/_product_row.html.haml index 9d2852a298b..15a9415e367 100644 --- a/app/views/admin/products_v3/_product_row.html.haml +++ b/app/views/admin/products_v3/_product_row.html.haml @@ -40,3 +40,4 @@ "data-modal-link-target-value": "product-delete-modal", "class": "delete", "data-modal-link-modal-dataset-value": {'data-delete-path': admin_product_destroy_path(product)}.to_json } = t('admin.products_page.actions.delete') + = link_to "Preview", admin_product_preview_path(product), "data-turbo-stream": "" diff --git a/app/views/admin/products_v3/product_preview.turbo_stream.haml b/app/views/admin/products_v3/product_preview.turbo_stream.haml new file mode 100644 index 00000000000..e0fff5c3480 --- /dev/null +++ b/app/views/admin/products_v3/product_preview.turbo_stream.haml @@ -0,0 +1,5 @@ += turbo_stream.replace "product-preview" do + #product-preview + This is where the product preview will be, id: + = @id + diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 2f6fa79d8bf..8036788d462 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -79,6 +79,8 @@ delete 'products_v3/:id', to: 'products_v3#destroy', as: 'product_destroy' delete 'products_v3/destroy_variant/:id', to: 'products_v3#destroy_variant', as: 'destroy_variant' post 'clone/:id', to: 'products_v3#clone', as: 'clone_product' + + resources :product_preview, only: [:show] end resources :variant_overrides do From 0a9b858f2aca3cff50e1c6660e947155287ec923 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Tue, 27 Aug 2024 14:30:05 +1000 Subject: [PATCH 03/26] Add the ability to pass options ModalComponent Now you can add another stimulus controller or action to the modal --- app/components/modal_component.rb | 6 +++- .../modal_component/modal_component.html.haml | 2 +- spec/components/modal_component_spec.rb | 36 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 spec/components/modal_component_spec.rb diff --git a/app/components/modal_component.rb b/app/components/modal_component.rb index 1de8852a8c7..f4b279278bd 100644 --- a/app/components/modal_component.rb +++ b/app/components/modal_component.rb @@ -1,11 +1,15 @@ # frozen_string_literal: true class ModalComponent < ViewComponent::Base - def initialize(id:, close_button: true, instant: false, modal_class: :small) + def initialize(id:, close_button: true, instant: false, modal_class: :small, **options) @id = id @close_button = close_button @instant = instant @modal_class = modal_class + @options = options + @data_controller = "modal #{@options.delete(:'data-controller')}".squish + @data_action = + "keyup@document->modal#closeIfEscapeKey #{@options.delete(:'data-action')}".squish end private diff --git a/app/components/modal_component/modal_component.html.haml b/app/components/modal_component/modal_component.html.haml index 7be73deca49..76a484c5285 100644 --- a/app/components/modal_component/modal_component.html.haml +++ b/app/components/modal_component/modal_component.html.haml @@ -1,4 +1,4 @@ -%div{ id: @id, "data-controller": "modal", "data-action": "keyup@document->modal#closeIfEscapeKey", "data-modal-instant-value": @instant } +%div{ id: @id, "data-controller": @data_controller, "data-action": @data_action, "data-modal-instant-value": @instant, **@options } .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 } = content diff --git a/spec/components/modal_component_spec.rb b/spec/components/modal_component_spec.rb new file mode 100644 index 00000000000..e50dbd3d1f1 --- /dev/null +++ b/spec/components/modal_component_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe ModalComponent, type: :component do + it "renders default 'data-action' and 'data-controller'" do + render_inline(described_class.new(id: "test-id")) + + expect(page).to have_selector "#test-id" + expect(page).to have_selector '[data-controller="modal"]' + expect(page).to have_selector '[data-action="keyup@document->modal#closeIfEscapeKey"]' + end + + it "accepts html attributes options" do + render_inline(described_class.new(id: "test-id", 'data-test': "some data")) + + expect(page).to have_selector "#test-id" + expect(page).to have_selector '[data-test="some data"]' + end + + it "merges 'data-controller' attribute when present in options" do + render_inline(described_class.new(id: "test-id", 'data-controller': "other-controller")) + + expect(page).to have_selector "#test-id" + expect(page).to have_selector '[data-controller="modal other-controller"]' + end + + it "merges 'data-action' attribute when present in options" do + render_inline(described_class.new(id: "test-id", 'data-action': "click->other-controller#test")) + + expect(page).to have_selector "#test-id" + expect(page).to have_selector( + '[data-action="keyup@document->modal#closeIfEscapeKey click->other-controller#test"]' + ) + end +end From 64d3091db9cc44f25a1c094f839000e8d16c3528 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Tue, 27 Aug 2024 14:32:42 +1000 Subject: [PATCH 04/26] Add product preview modal Plus open modal when clicking on "preview" link. It's using event to communicate between stimulus controller : https://stimulus.hotwired.dev/reference/controllers#cross-controller-coordination-with-events --- app/views/admin/products_v3/_product_row.html.haml | 2 +- app/views/admin/products_v3/index.html.haml | 3 +++ app/webpacker/controllers/product_preview_controller.js | 9 +++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 app/webpacker/controllers/product_preview_controller.js diff --git a/app/views/admin/products_v3/_product_row.html.haml b/app/views/admin/products_v3/_product_row.html.haml index 15a9415e367..b35fb32472c 100644 --- a/app/views/admin/products_v3/_product_row.html.haml +++ b/app/views/admin/products_v3/_product_row.html.haml @@ -40,4 +40,4 @@ "data-modal-link-target-value": "product-delete-modal", "class": "delete", "data-modal-link-modal-dataset-value": {'data-delete-path': admin_product_destroy_path(product)}.to_json } = t('admin.products_page.actions.delete') - = link_to "Preview", admin_product_preview_path(product), "data-turbo-stream": "" + = link_to "Preview", admin_product_preview_path(product), {"data-turbo-stream": "", "data-controller": "product-preview", "data-action": "click->product-preview#open" } # TODO move product-preview controller to table element ? diff --git a/app/views/admin/products_v3/index.html.haml b/app/views/admin/products_v3/index.html.haml index 668a8527a4b..171b214153f 100644 --- a/app/views/admin/products_v3/index.html.haml +++ b/app/views/admin/products_v3/index.html.haml @@ -20,3 +20,6 @@ = render partial: 'delete_modal', locals: { object_type: } #modal-component #edit_image_modal + + = render ModalComponent.new(id: "product-preview-modal", "data-action": "product-preview:open@window->modal#open") do + #product-preview diff --git a/app/webpacker/controllers/product_preview_controller.js b/app/webpacker/controllers/product_preview_controller.js new file mode 100644 index 00000000000..496b54eaafd --- /dev/null +++ b/app/webpacker/controllers/product_preview_controller.js @@ -0,0 +1,9 @@ +import { Controller } from "stimulus"; + +export default class extends Controller { + open() { + // dispatch "product-preview:open" event to trigger modal->open action + // see views/admin/product_v3/index.html.haml + this.dispatch("open"); + } +} From 561f4648d2d4efa9c6e3bb0bbe3ead5253bb91c4 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Wed, 28 Aug 2024 11:44:42 +1000 Subject: [PATCH 05/26] Improve tooltip partial Set up default value optiona locals variable --- app/views/admin/shared/_tooltip.html.haml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/views/admin/shared/_tooltip.html.haml b/app/views/admin/shared/_tooltip.html.haml index 6e28c08c433..ca6f98310b6 100644 --- a/app/views/admin/shared/_tooltip.html.haml +++ b/app/views/admin/shared/_tooltip.html.haml @@ -1,5 +1,8 @@ -%div{"data-controller": "tooltip"} - %a{"data-tooltip-target": "element", href: link, class: link_class} +- tooltip_placement = defined?(placement) ? placement : "top" +- tooltip_link = defined?(link) ? link : "" +- tooltip_link_class = defined?(link_class) ? link_class : "" +%div{"data-controller": "tooltip", "data-tooltip-placement-value": tooltip_placement } + %a{"data-tooltip-target": "element", href: tooltip_link, class: tooltip_link_class} = link_text .tooltip-container .tooltip{"data-tooltip-target": "tooltip"} From 27dd5def575325743838cd42d0cdc2eb9c5bb611 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Wed, 28 Aug 2024 11:47:35 +1000 Subject: [PATCH 06/26] Open modal before rendering the received html This way we don't see a blank modal waiting for the content to load --- app/views/admin/products_v3/_product_row.html.haml | 2 +- app/views/admin/products_v3/_table.html.haml | 2 +- .../controllers/product_preview_controller.js | 14 +++++++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/views/admin/products_v3/_product_row.html.haml b/app/views/admin/products_v3/_product_row.html.haml index b35fb32472c..5fda0094795 100644 --- a/app/views/admin/products_v3/_product_row.html.haml +++ b/app/views/admin/products_v3/_product_row.html.haml @@ -40,4 +40,4 @@ "data-modal-link-target-value": "product-delete-modal", "class": "delete", "data-modal-link-modal-dataset-value": {'data-delete-path': admin_product_destroy_path(product)}.to_json } = t('admin.products_page.actions.delete') - = link_to "Preview", admin_product_preview_path(product), {"data-turbo-stream": "", "data-controller": "product-preview", "data-action": "click->product-preview#open" } # TODO move product-preview controller to table element ? + = link_to "Preview", admin_product_preview_path(product), {"data-turbo-stream": "" } diff --git a/app/views/admin/products_v3/_table.html.haml b/app/views/admin/products_v3/_table.html.haml index b45eabab72a..40a6858f342 100644 --- a/app/views/admin/products_v3/_table.html.haml +++ b/app/views/admin/products_v3/_table.html.haml @@ -13,7 +13,7 @@ = hidden_field_tag :producer_id, @producer_id = hidden_field_tag :category_id, @category_id - %table.products{ 'data-column-preferences-target': "table" } + %table.products{ 'data-column-preferences-target': "table", 'data-controller': "product-preview" } %colgroup -# The `min-width` property works in Chrome but not Firefox so is considered progressive enhancement. %col.col-image{ width:"44px" }= # (image size + padding) diff --git a/app/webpacker/controllers/product_preview_controller.js b/app/webpacker/controllers/product_preview_controller.js index 496b54eaafd..cbb1551f014 100644 --- a/app/webpacker/controllers/product_preview_controller.js +++ b/app/webpacker/controllers/product_preview_controller.js @@ -1,7 +1,19 @@ import { Controller } from "stimulus"; export default class extends Controller { - open() { + connect() { + // open the modal before rendering the html to avoid opening a blank modal + // TODO other option is having a controller in the stream html that would dispatch the open element on connect + window.addEventListener("turbo:before-stream-render", this.#open.bind(this)); + } + + disconnect() { + window.removeEventListener("turbo:before-stream-render", this.#open.bind(this)); + } + + // private + + #open() { // dispatch "product-preview:open" event to trigger modal->open action // see views/admin/product_v3/index.html.haml this.dispatch("open"); From f24a4edc68e9891dfc3db018499a63a4aa430fef Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Wed, 28 Aug 2024 11:51:09 +1000 Subject: [PATCH 07/26] Add product detail to the modal --- .../admin/product_preview_controller.rb | 6 +-- .../product_preview.turbo_stream.haml | 37 ++++++++++++++++++- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/app/controllers/admin/product_preview_controller.rb b/app/controllers/admin/product_preview_controller.rb index 27e21a27844..2ea48d74fc7 100644 --- a/app/controllers/admin/product_preview_controller.rb +++ b/app/controllers/admin/product_preview_controller.rb @@ -3,11 +3,11 @@ module Admin class ProductPreviewController < Spree::Admin::BaseController def show - @id = params[:id] - # TODO load product data based on param + @product = Spree::Product.find(params[:id]) + respond_with do |format| format.turbo_stream { - render "admin/products_v3/product_preview", status: :ok, locals: { id: @id } + render "admin/products_v3/product_preview", status: :ok } end end diff --git a/app/views/admin/products_v3/product_preview.turbo_stream.haml b/app/views/admin/products_v3/product_preview.turbo_stream.haml index e0fff5c3480..2045ea5910d 100644 --- a/app/views/admin/products_v3/product_preview.turbo_stream.haml +++ b/app/views/admin/products_v3/product_preview.turbo_stream.haml @@ -1,5 +1,38 @@ = turbo_stream.replace "product-preview" do #product-preview - This is where the product preview will be, id: - = @id + .row + .columns.small-12.medium-6.large-6.product-header + %h3 + = @product.name + %span + %em + = t("products_from") + %span + = @product.variants.first.supplier.name + %br- + + .filter-shopfront.property-selectors.inline-block + %ul + - @product.properties_including_inherited.each do |property| + %li + %span + - if property[:value].present? + = render partial: "admin/shared/tooltip", locals: { tooltip_text: property[:value], link_text: property[:name], placement: "bottom" } + - else + %a + = property[:name] + + + - if @product.description + .product-description + - # TODO description not wrapped properly + %p.text-small{ 'data-controller': "add-blank-to-link" } + - # description is sanitized in Spree::Product#description method + = @product.description.html_safe + + .columns.small-12.medium-6.large-6.product-img + - if @product.image + %img{ src: @product.image.url(:large) } + -else + %img.placeholder{ src: Spree::Image.default_image_url(:large) } From d56ab9257ba6a21e3e988529bc2c01678c7de791 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Tue, 3 Sep 2024 13:38:05 +1000 Subject: [PATCH 08/26] Add tab switch and shop tab --- app/views/admin/products_v3/index.html.haml | 2 +- .../product_preview.turbo_stream.haml | 155 ++++++++++++++---- config/locales/en.yml | 4 + 3 files changed, 124 insertions(+), 37 deletions(-) diff --git a/app/views/admin/products_v3/index.html.haml b/app/views/admin/products_v3/index.html.haml index 171b214153f..4e349481231 100644 --- a/app/views/admin/products_v3/index.html.haml +++ b/app/views/admin/products_v3/index.html.haml @@ -21,5 +21,5 @@ #modal-component #edit_image_modal - = render ModalComponent.new(id: "product-preview-modal", "data-action": "product-preview:open@window->modal#open") do + = render ModalComponent.new(id: "product-preview-modal", modal_class: "big", "data-action": "product-preview:open@window->modal#open") do #product-preview diff --git a/app/views/admin/products_v3/product_preview.turbo_stream.haml b/app/views/admin/products_v3/product_preview.turbo_stream.haml index 2045ea5910d..d1498775a34 100644 --- a/app/views/admin/products_v3/product_preview.turbo_stream.haml +++ b/app/views/admin/products_v3/product_preview.turbo_stream.haml @@ -1,38 +1,121 @@ = turbo_stream.replace "product-preview" do - #product-preview - .row - .columns.small-12.medium-6.large-6.product-header - %h3 - = @product.name - %span - %em - = t("products_from") - %span - = @product.variants.first.supplier.name - - %br- - - .filter-shopfront.property-selectors.inline-block - %ul - - @product.properties_including_inherited.each do |property| - %li + #product-preview{ "data-controller": "tabs" } + %h1 + = t("admin.products_page.product_preview.product_preview") + %dl.tabs + %dd + %a{ data: { "tabs-target": "tab", "action": "tabs#select" } } + = t("admin.products_page.product_preview.shop_tab") + %dd + %a{ data: { "tabs-target": "tab", "action": "tabs#select" } } + = t("admin.products_page.product_preview.product_details_tab") + .tabs-content + .content.active + %div{ data: { "tabs-target": "content" } } + .product-thumb + %a + - if @product.group_buy + %span.product-thumb__bulk-label + = t(".bulk") + = image_tag @product.image&.url(:small) || Spree::Image.default_image_url(:small) + + .summary + .summary-header + %h3 + %a + %span + = @product.name + - if @product.description + .product-description{ "data-controller": "add-blank-to-link" } + - # description is sanitized in Spree::Product#description method + = @product.description.html_safe + + - if @product.variants.first.supplier.visible + %div + .product-producer + = t :products_from + %span + = @product.variants.first.supplier.name + .filter-shopfront.property-selectors.inline-block + %ul + - @product.properties_including_inherited.each do |property| + %li + - if property[:value].present? + = render partial: "admin/shared/tooltip", locals: { tooltip_text: property[:value], link_text: property[:name], placement: "bottom" } + - else + %a + %span + = property[:name] + .shop-variants + - @product.variants.sort { |v1, v2| v1.name_to_display <=> v2.name_to_display }.sort { |v1, v2| v1.unit_value <=> v2.unit_value }.each do |variant| + .variants.row + .small-3.columns.variant-name + - if variant.display_name + .inline + = variant.display_name + .variant-unit + = variant.unit_to_display + .small-4.medium-3.columns.variant-price + = number_to_currency(variant.price) + .unit-price.variant-unit-price + -# TODO tool tip + = render partial: "admin/shared/tooltip", locals: { tooltip_text: t("js.shopfront.unit_price_tooltip"), link_text: "?", link: "#", link_class: "question-mark-icon", placement: "top" } + - # TODO use an helper + - unit_price = UnitPrice.new(variant) + - price_per_unit = variant.price / (unit_price.denominator || 1) + = "#{number_to_currency(price_per_unit)} / #{unit_price.unit}".html_safe + + + .medium-3.columns.total-price + %span + = number_to_currency(0.00) + .small-5.medium-3.large-3.columns.variant-quantity-column.text-right + .variant-quantity-inputs + %button.add-variant{ disabled: true} + = t("js.shopfront.variant.add_to_cart") + + - # TODO can't check the shop preferrence here, display by default ? + - if !variant.on_demand && variant.on_hand <= 3 + .variant-remaining-stock + = t("js.shopfront.variant.remaining_in_stock", quantity: variant.on_hand) + + .product-properties.filter-shopfront.property-selectors + %filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" } + + %div{ data: { "tabs-target": "content" } } + .row + .columns.small-12.medium-6.large-6.product-header + %h3 + = @product.name + %span + %em + = t("products_from") %span - - if property[:value].present? - = render partial: "admin/shared/tooltip", locals: { tooltip_text: property[:value], link_text: property[:name], placement: "bottom" } - - else - %a - = property[:name] - - - - if @product.description - .product-description - - # TODO description not wrapped properly - %p.text-small{ 'data-controller': "add-blank-to-link" } - - # description is sanitized in Spree::Product#description method - = @product.description.html_safe - - .columns.small-12.medium-6.large-6.product-img - - if @product.image - %img{ src: @product.image.url(:large) } - -else - %img.placeholder{ src: Spree::Image.default_image_url(:large) } + = @product.variants.first.supplier.name + + %br + + .filter-shopfront.property-selectors.inline-block + %ul + - @product.properties_including_inherited.each do |property| + %li + - if property[:value].present? + = render partial: "admin/shared/tooltip", locals: { tooltip_text: property[:value], link_text: property[:name], placement: "bottom" } + - else + %a + %span + = property[:name] + + + - if @product.description + .product-description + - # TODO description not wrapped properly + %p.text-small{ 'data-controller': "add-blank-to-link" } + - # description is sanitized in Spree::Product#description method + = @product.description.html_safe + + .columns.small-12.medium-6.large-6.product-img + - if @product.image + %img{ src: @product.image.url(:large) } + -else + %img.placeholder{ src: Spree::Image.default_image_url(:large) } diff --git a/config/locales/en.yml b/config/locales/en.yml index f34c6fc2c32..00465679017 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -606,6 +606,10 @@ en: remove: Remove image: edit: Edit + product_preview: + product_preview: Product preview + shop_tab: Shop + product_details_tab: Product details adjustments: skipped_changing_canceled_order: "You can't change a cancelled order." # Common properties / models From 2b74bbd45d721b86b292483c316e5fa62fee9720 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Tue, 3 Sep 2024 14:58:06 +1000 Subject: [PATCH 09/26] Add styling for tabs --- app/webpacker/css/admin_v3/all.scss | 2 + .../css/admin_v3/pages/product_preview.scss | 41 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 app/webpacker/css/admin_v3/pages/product_preview.scss diff --git a/app/webpacker/css/admin_v3/all.scss b/app/webpacker/css/admin_v3/all.scss index bdba91568cb..808a426caab 100644 --- a/app/webpacker/css/admin_v3/all.scss +++ b/app/webpacker/css/admin_v3/all.scss @@ -132,3 +132,5 @@ @import "app/webpacker/css/admin/trix.scss"; @import "terms_of_service_banner"; // admin_v3 + +@import "pages/product_preview"; // admin_v3 diff --git a/app/webpacker/css/admin_v3/pages/product_preview.scss b/app/webpacker/css/admin_v3/pages/product_preview.scss new file mode 100644 index 00000000000..07a0f617bc0 --- /dev/null +++ b/app/webpacker/css/admin_v3/pages/product_preview.scss @@ -0,0 +1,41 @@ +#product-preview { + // tabs + dl.tabs { + box-shadow: $box-shadow; + display: flex; + flex-wrap: wrap; + + dd { + width: auto; + padding: 0; + + a { + display: inline-block; + padding: 16px 20px; + color: $color-9 !important; + text-align: center; + position: relative; + font-size: 16px; + font-weight: 600; + + &:hover { + color: $red !important; + + &:after { + content: ""; + position: absolute; + bottom: 0; + left: 20px; + right: 20px; + height: 3px; + background: $red; + } + } + + &.active { + @extend a, :hover; + } + } + } + } +} From f59ee96011512d6765e033e80c4dbde1e16a1bfb Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Tue, 3 Sep 2024 15:19:14 +1000 Subject: [PATCH 10/26] Copy foundation-sites css relevant to the modal The frontend is based on fondation-sites to provide responsive design, we can't just import in the backend. So opted for copying the part we needed --- .../css/admin_v3/pages/product_preview.scss | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/app/webpacker/css/admin_v3/pages/product_preview.scss b/app/webpacker/css/admin_v3/pages/product_preview.scss index 07a0f617bc0..319d733bb14 100644 --- a/app/webpacker/css/admin_v3/pages/product_preview.scss +++ b/app/webpacker/css/admin_v3/pages/product_preview.scss @@ -38,4 +38,63 @@ } } } + + // The frontend css is base on foundation-sites https://github.com/foundation/foundation-sites + // Below we copied the sections that are relevant to the product preview modal + + // from foundation-sites/scss/foundations/components/_types.scss + h1, + h2, + h3, + h4, + h5, + h6 { + color: #222222; + font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; + font-style: normal; + font-weight: normal; + line-height: 1.4; + margin-bottom: 0.5rem; + margin-top: 0.2rem; + text-rendering: optimizeLegibility; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + line-height: 1.4; + } + + h3 { + font-size: 1.6875rem; + } + + em, + i { + font-style: italic; + line-height: inherit; + } + + ul, + ol, + dl { + font-family: inherit; + font-size: 1rem; + line-height: 1.6; + list-style-position: outside; + margin-bottom: 1.25rem; + } + + // from fondation-sites/scss/foundations/components/_grid.scss + .column + .column:last-child, + .column + .columns:last-child, + .columns + .column:last-child, + .columns + .columns:last-child { + float: right; + } + } + From 55733555bf03214e539c3cc7d051f64bc1b99e1a Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Wed, 4 Sep 2024 11:01:39 +1000 Subject: [PATCH 11/26] Add style for Product details Only import relevant css, which has been move to their own partial to avoid duplication --- .../css/admin_v3/pages/product_preview.scss | 28 +++- .../css/darkswarm/_shop-filters.scss | 120 +----------------- app/webpacker/css/darkswarm/animations.scss | 9 +- app/webpacker/css/darkswarm/images.scss | 21 +-- .../darkswarm/shop_partials/_animations.scss | 8 ++ .../css/darkswarm/shop_partials/_images.scss | 21 +++ .../shop_partials/_shop-filters.scss | 119 +++++++++++++++++ .../darkswarm/shop_partials/_typography.scss | 31 +++++ app/webpacker/css/darkswarm/typography.scss | 31 +---- 9 files changed, 208 insertions(+), 180 deletions(-) create mode 100644 app/webpacker/css/darkswarm/shop_partials/_animations.scss create mode 100644 app/webpacker/css/darkswarm/shop_partials/_images.scss create mode 100644 app/webpacker/css/darkswarm/shop_partials/_shop-filters.scss create mode 100644 app/webpacker/css/darkswarm/shop_partials/_typography.scss diff --git a/app/webpacker/css/admin_v3/pages/product_preview.scss b/app/webpacker/css/admin_v3/pages/product_preview.scss index 319d733bb14..78250a8648b 100644 --- a/app/webpacker/css/admin_v3/pages/product_preview.scss +++ b/app/webpacker/css/admin_v3/pages/product_preview.scss @@ -1,3 +1,6 @@ +@import "../../darkswarm/branding"; +@import "../../darkswarm/mixins"; + #product-preview { // tabs dl.tabs { @@ -40,7 +43,7 @@ } // The frontend css is base on foundation-sites https://github.com/foundation/foundation-sites - // Below we copied the sections that are relevant to the product preview modal + // Below we copied the sections that are relevant to the product preview modal // from foundation-sites/scss/foundations/components/_types.scss h1, @@ -88,7 +91,7 @@ margin-bottom: 1.25rem; } - // from fondation-sites/scss/foundations/components/_grid.scss + // from foundation-sites/scss/foundations/components/_grid.scss .column + .column:last-child, .column + .columns:last-child, .columns + .column:last-child, @@ -96,5 +99,24 @@ float: right; } -} + // from foundation-sites/scss/foundations/components/_global.scss + img { + display: inline-block; + vertical-align: middle; + } + + // Import frontend partials + @import "../../darkswarm/shop_partials/typography"; + @import "../../darkswarm/overrides"; + // product preview override + .row { + margin: 0 auto; + width: 100%; + } + + @import "../../darkswarm/shop_partials/animations"; + @import "../../darkswarm/shop_partials/shop-filters"; + @import "../../darkswarm/shop-modals"; + @import "../../darkswarm/shop_partials/images"; +} diff --git a/app/webpacker/css/darkswarm/_shop-filters.scss b/app/webpacker/css/darkswarm/_shop-filters.scss index e4bceed102d..f6de8c8536c 100644 --- a/app/webpacker/css/darkswarm/_shop-filters.scss +++ b/app/webpacker/css/darkswarm/_shop-filters.scss @@ -1,102 +1,4 @@ - -@mixin filter-selector($base-clr, $border-clr, $hover-clr) { - &.inline-block, ul.inline-block { - display: inline-block; - } - - li { - display: inline-block; - - @include border-radius(0); - - padding: 0; - margin: 0 0.5rem 0.5rem 0; - - &:hover, &:focus { - background: transparent; - } - - &.active { - box-shadow: none; - } - - a, a.button { - display: block; - - @include border-radius(0.5em); - - border: 1px solid $border-clr; - padding: 0.5em 0.625em; - color: $base-clr; - font-size: 0.75em; - background: white; - margin: 0; - - i { - padding-left: 0.25rem; - } - - render-svg { - &, & svg { - width: 1rem; - height: 1rem; - float: left; - padding-right: 0.25rem; - - path { - @include csstrans; - - fill: $base-clr; - } - } - } - - &:hover, &:focus { - border-color: $hover-clr; - color: $hover-clr; - - render-svg { - svg { - path { - fill: $hover-clr; - } - } - } - } - - &.disabled { - opacity: 0.6; - - &:hover, &:focus { - border-color: $border-clr; - color: $base-clr; - - render-svg { - svg { - path { - fill: $base-clr; - } - } - } - } - } - - &.active, &.active:hover, &.active:focus { - border: 1px solid $base-clr; - background: $base-clr; - color: white; - - render-svg { - svg { - path { - fill: white; - } - } - } - } - } - } -} +@import "shop_partials/shop-filters"; // Alert when search, taxon, filter is triggered @@ -167,23 +69,3 @@ max-height: calc(100vh - #{$topbar-height}); overflow-y: auto; } - -.filter-shopfront { - &.taxon-selectors, &.property-selectors { - background: transparent; - } - - // Shopfront taxons - &.taxon-selectors { - @include filter-selector($clr-blue, $clr-blue-light, $clr-blue-bright); - } - - // Shopfront properties - &.property-selectors { - @include filter-selector(#666, #ccc, #777); - } - - ul { - margin: 0; - } -} diff --git a/app/webpacker/css/darkswarm/animations.scss b/app/webpacker/css/darkswarm/animations.scss index 9fdb17eec72..bfe0b5b681e 100644 --- a/app/webpacker/css/darkswarm/animations.scss +++ b/app/webpacker/css/darkswarm/animations.scss @@ -275,11 +275,4 @@ product.animate-repeat { } } -@mixin csstrans { - -webkit-transition: all 300ms ease; - -moz-transition: all 300ms ease; - -ms-transition: all 300ms ease; - -o-transition: all 300ms ease; - transition: all 300ms ease; - -webkit-transform-style: preserve-3d; -} +@import "shop_partials/animations"; diff --git a/app/webpacker/css/darkswarm/images.scss b/app/webpacker/css/darkswarm/images.scss index 9019971bfb2..72780d5a075 100644 --- a/app/webpacker/css/darkswarm/images.scss +++ b/app/webpacker/css/darkswarm/images.scss @@ -1,23 +1,4 @@ -.product-img { - text-align: center; - - img { - padding: 0.3rem; - - // placeholder for when no product images - &.placeholder { - opacity: 0.35; - - @include breakpoint(desktop) { - display: none; - } - } - - @media only screen and (max-width: 1024px) { - margin: 0 0 0.5rem; - } - } -} +//@import "shop_partials/images"; .hero-img { outline: 1px solid $disabled-bright; diff --git a/app/webpacker/css/darkswarm/shop_partials/_animations.scss b/app/webpacker/css/darkswarm/shop_partials/_animations.scss new file mode 100644 index 00000000000..ab06239fc8a --- /dev/null +++ b/app/webpacker/css/darkswarm/shop_partials/_animations.scss @@ -0,0 +1,8 @@ +@mixin csstrans { + -webkit-transition: all 300ms ease; + -moz-transition: all 300ms ease; + -ms-transition: all 300ms ease; + -o-transition: all 300ms ease; + transition: all 300ms ease; + -webkit-transform-style: preserve-3d; +} diff --git a/app/webpacker/css/darkswarm/shop_partials/_images.scss b/app/webpacker/css/darkswarm/shop_partials/_images.scss new file mode 100644 index 00000000000..a08504547c2 --- /dev/null +++ b/app/webpacker/css/darkswarm/shop_partials/_images.scss @@ -0,0 +1,21 @@ +.product-img { + text-align: center; + + img { + padding: 0.3rem; + + // placeholder for when no product images + &.placeholder { + opacity: 0.35; + + @include breakpoint(desktop) { + display: none; + } + } + + @media only screen and (max-width: 1024px) { + margin: 0 0 0.5rem; + } + } +} + diff --git a/app/webpacker/css/darkswarm/shop_partials/_shop-filters.scss b/app/webpacker/css/darkswarm/shop_partials/_shop-filters.scss new file mode 100644 index 00000000000..5a05979d7f5 --- /dev/null +++ b/app/webpacker/css/darkswarm/shop_partials/_shop-filters.scss @@ -0,0 +1,119 @@ +@mixin filter-selector($base-clr, $border-clr, $hover-clr) { + &.inline-block, ul.inline-block { + display: inline-block; + } + + li { + display: inline-block; + + @include border-radius(0); + + padding: 0; + margin: 0 0.5rem 0.5rem 0; + + &:hover, &:focus { + background: transparent; + } + + &.active { + box-shadow: none; + } + + a, a.button { + display: block; + + @include border-radius(0.5em); + + border: 1px solid $border-clr; + padding: 0.5em 0.625em; + color: $base-clr; + font-size: 0.75em; + background: white; + margin: 0; + + i { + padding-left: 0.25rem; + } + + render-svg { + &, & svg { + width: 1rem; + height: 1rem; + float: left; + padding-right: 0.25rem; + + path { + @include csstrans; + + fill: $base-clr; + } + } + } + + &:hover, &:focus { + border-color: $hover-clr; + color: $hover-clr; + + render-svg { + svg { + path { + fill: $hover-clr; + } + } + } + } + + &.disabled { + opacity: 0.6; + + &:hover, &:focus { + border-color: $border-clr; + color: $base-clr; + + render-svg { + svg { + path { + fill: $base-clr; + } + } + } + } + } + + &.active, &.active:hover, &.active:focus { + border: 1px solid $base-clr; + background: $base-clr; + color: white; + + render-svg { + svg { + path { + fill: white; + } + } + } + } + } + } +} + +.filter-shopfront { + &.taxon-selectors, &.property-selectors { + background: transparent; + } + + // Shopfront taxons + &.taxon-selectors { + @include filter-selector($clr-blue, $clr-blue-light, $clr-blue-bright); + } + + // Shopfront properties + &.property-selectors { + @include filter-selector(#666, #ccc, #777); + } + + ul { + margin: 0; + } +} + diff --git a/app/webpacker/css/darkswarm/shop_partials/_typography.scss b/app/webpacker/css/darkswarm/shop_partials/_typography.scss new file mode 100644 index 00000000000..5160a1c074f --- /dev/null +++ b/app/webpacker/css/darkswarm/shop_partials/_typography.scss @@ -0,0 +1,31 @@ +@mixin headingFont { + font-family: "Oswald", sans-serif; +} + +// TODO should probably move that to a variables.scss +$body-font: "Roboto", Arial, sans-serif; + +h1, h2, h3, h4, h5, h6 { + @include headingFont; + + padding: 0px; +} + +a { + color: $clr-brick; + + &:hover, &:focus, &:active { + text-decoration: none; + color: $clr-brick-bright; + } +} + +.text-small { + font-size: 0.875rem; + margin-bottom: 0.5rem; + font-family: $body-font; + + &, & * { + font-size: 0.875rem; + } +} diff --git a/app/webpacker/css/darkswarm/typography.scss b/app/webpacker/css/darkswarm/typography.scss index 2261036d746..2740288f0c9 100644 --- a/app/webpacker/css/darkswarm/typography.scss +++ b/app/webpacker/css/darkswarm/typography.scss @@ -1,14 +1,10 @@ - -@mixin headingFont { - font-family: "Oswald", sans-serif; -} +@import "shop_partials/typography"; @mixin bodyFont { font-family: "Roboto", Arial, sans-serif; } $headingFont: "Oswald"; -$body-font: "Roboto", Arial, sans-serif; body { @include bodyFont; @@ -16,15 +12,6 @@ body { font-weight: 400; } -a { - color: $clr-brick; - - &:hover, &:focus, &:active { - text-decoration: none; - color: $clr-brick-bright; - } -} - .text-vbig { font-size: 2rem; font-weight: 300; @@ -39,16 +26,6 @@ small, .small { font-size: 0.75rem; } -.text-small { - font-size: 0.875rem; - margin-bottom: 0.5rem; - font-family: $body-font; - - &, & * { - font-size: 0.875rem; - } -} - .text-normal { font-weight: 400; font-family: $body-font; @@ -92,12 +69,6 @@ small, .small { border-color: rgba(#ddd, 0.25); } -h1, h2, h3, h4, h5, h6 { - @include headingFont; - - padding: 0px; -} - .inline-header { display: inline-block; margin: 0px; From e8de76dc46dd9830035075fbe6dc2d3527cb5eb5 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Wed, 4 Sep 2024 15:24:29 +1000 Subject: [PATCH 12/26] Add style for Shop As before, move imported css to partials to avoid duplication. And use mixin and variable to handle tooltip styling --- .../product_preview.turbo_stream.haml | 42 ++-- .../css/admin_v3/pages/product_preview.scss | 75 ++++++- app/webpacker/css/darkswarm/_shop-inputs.scss | 76 +------ .../css/darkswarm/_shop-product-rows.scss | 196 +----------------- .../css/darkswarm/_shop-product-thumb.scss | 29 +-- .../darkswarm/shop_partials/_shop-inputs.scss | 76 +++++++ .../shop_partials/_shop-product-rows.scss | 195 +++++++++++++++++ .../shop_partials/_shop-product-thumb.scss | 28 +++ .../css/shared/question-mark-icon.scss | 20 +- 9 files changed, 409 insertions(+), 328 deletions(-) create mode 100644 app/webpacker/css/darkswarm/shop_partials/_shop-inputs.scss create mode 100644 app/webpacker/css/darkswarm/shop_partials/_shop-product-rows.scss create mode 100644 app/webpacker/css/darkswarm/shop_partials/_shop-product-thumb.scss diff --git a/app/views/admin/products_v3/product_preview.turbo_stream.haml b/app/views/admin/products_v3/product_preview.turbo_stream.haml index d1498775a34..ca4e62b535d 100644 --- a/app/views/admin/products_v3/product_preview.turbo_stream.haml +++ b/app/views/admin/products_v3/product_preview.turbo_stream.haml @@ -35,22 +35,24 @@ .product-producer = t :products_from %span - = @product.variants.first.supplier.name - .filter-shopfront.property-selectors.inline-block - %ul - - @product.properties_including_inherited.each do |property| - %li - - if property[:value].present? - = render partial: "admin/shared/tooltip", locals: { tooltip_text: property[:value], link_text: property[:name], placement: "bottom" } - - else - %a - %span - = property[:name] + %a + = @product.variants.first.supplier.name + .product-properties.filter-shopfront.property-selectors + .filter-shopfront.property-selectors.inline-block + %ul + - @product.properties_including_inherited.each do |property| + %li + - if property[:value].present? + = render partial: "admin/shared/tooltip", locals: { tooltip_text: property[:value], link_text: property[:name], placement: "bottom" } + - else + %a + %span + = property[:name] .shop-variants - @product.variants.sort { |v1, v2| v1.name_to_display <=> v2.name_to_display }.sort { |v1, v2| v1.unit_value <=> v2.unit_value }.each do |variant| .variants.row .small-3.columns.variant-name - - if variant.display_name + - if variant.display_name.present? .inline = variant.display_name .variant-unit @@ -58,8 +60,7 @@ .small-4.medium-3.columns.variant-price = number_to_currency(variant.price) .unit-price.variant-unit-price - -# TODO tool tip - = render partial: "admin/shared/tooltip", locals: { tooltip_text: t("js.shopfront.unit_price_tooltip"), link_text: "?", link: "#", link_class: "question-mark-icon", placement: "top" } + = render partial: "admin/shared/tooltip", locals: { tooltip_text: t("js.shopfront.unit_price_tooltip"), link_text: "", link: "", link_class: "question-mark-icon", placement: "top" } - # TODO use an helper - unit_price = UnitPrice.new(variant) - price_per_unit = variant.price / (unit_price.denominator || 1) @@ -71,16 +72,13 @@ = number_to_currency(0.00) .small-5.medium-3.large-3.columns.variant-quantity-column.text-right .variant-quantity-inputs - %button.add-variant{ disabled: true} + %button.add-variant = t("js.shopfront.variant.add_to_cart") - - # TODO can't check the shop preferrence here, display by default ? - - if !variant.on_demand && variant.on_hand <= 3 - .variant-remaining-stock - = t("js.shopfront.variant.remaining_in_stock", quantity: variant.on_hand) - - .product-properties.filter-shopfront.property-selectors - %filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" } + - # TODO can't check the shop preferrence here, display by default ? + - if !variant.on_demand && variant.on_hand <= 3 + .variant-remaining-stock + = t("js.shopfront.variant.remaining_in_stock", quantity: variant.on_hand) %div{ data: { "tabs-target": "content" } } .row diff --git a/app/webpacker/css/admin_v3/pages/product_preview.scss b/app/webpacker/css/admin_v3/pages/product_preview.scss index 78250a8648b..90fa84c80d2 100644 --- a/app/webpacker/css/admin_v3/pages/product_preview.scss +++ b/app/webpacker/css/admin_v3/pages/product_preview.scss @@ -91,6 +91,35 @@ margin-bottom: 1.25rem; } + .text-right { + text-align: right !important; + } + + // from foundation-sites/scss/foundations/components/_buttons.scss + button, + .button { + -webkit-appearance: none; + -moz-appearance: none; + border-radius: 0; + border-style: solid; + border-width: 0; + cursor: pointer; + font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; + font-weight: normal; + line-height: normal; + margin: 0 0 1.25rem; + position: relative; + text-align: center; + text-decoration: none; + display: inline-block; + padding: 1rem 2rem 1.0625rem 2rem; + font-size: 1rem; + background-color: #008cba; + border-color: #007095; + color: #ffffff; + transition: background-color 300ms ease-out; + } + // from foundation-sites/scss/foundations/components/_grid.scss .column + .column:last-child, .column + .columns:last-child, @@ -99,6 +128,22 @@ float: right; } + .small-3 { + width: 25%; + } + + @media only screen and (min-width: 40.0625em) { + .medium-3 { + width: 20%; // original value 25% + } + } + + @media only screen and (min-width: 64.0625em) { + .large-3 { + width: 25%; + } + } + // from foundation-sites/scss/foundations/components/_global.scss img { display: inline-block; @@ -106,10 +151,11 @@ } // Import frontend partials + + // Product details @import "../../darkswarm/shop_partials/typography"; @import "../../darkswarm/overrides"; - // product preview override .row { margin: 0 auto; width: 100%; @@ -119,4 +165,31 @@ @import "../../darkswarm/shop_partials/shop-filters"; @import "../../darkswarm/shop-modals"; @import "../../darkswarm/shop_partials/images"; + + // Shop + @import "../../darkswarm/shop_partials/shop-product-thumb"; + @import "../../darkswarm/shop_partials/shop-product-rows"; + @import "../../darkswarm/shop_partials/shop-inputs"; + @import "../../shared/question-mark-icon"; + + button.add-variant { + opacity: 0.5; + } + + .variant-remaining-stock { + opacity: 0.5; + } + + .question-mark-icon { + display: block; + } + + .tooltip { + @include joyride-content; + width: $joyride-width; + } + + .arrow { + background-color: $dynamic-blue; + } } diff --git a/app/webpacker/css/darkswarm/_shop-inputs.scss b/app/webpacker/css/darkswarm/_shop-inputs.scss index 98f4f7fecfd..26c28c2fa97 100644 --- a/app/webpacker/css/darkswarm/_shop-inputs.scss +++ b/app/webpacker/css/darkswarm/_shop-inputs.scss @@ -15,81 +15,7 @@ // // They are not nested so that they can be used in modals. -.variant-quantity-inputs { - height: 2.5rem; - white-space: nowrap; -} - -button.add-variant, button.variant-quantity { - height: 2.5rem; - border-radius: 0; - background-color: $orange-500; - color: white; - // Override foundation button styles: - font-size: 1rem; - margin: 0; - padding: 0; - transition: none; - - &:hover { - background-color: $orange-600; - } - - &[disabled] { - background-color: $grey-400; - - &:hover, &:focus { - background-color: $grey-400; - } - } - &:nth-of-type(1) { - border-bottom-left-radius: 0.25em; - border-top-left-radius: 0.25em; - } - &:nth-last-of-type(1) { - border-top-right-radius: 0.25em; - border-bottom-right-radius: 0.25em; - } -} - -button.add-variant { - min-width: 7rem; - padding: 0 1em; - - &[disabled] { - &:hover, &:focus { - background-color: $orange-500; - } - } -} - -button.variant-quantity { - width: 2.25rem; - - &:nth-of-type(1):not(.bulk-buy):not(.bulk-buy-add) { - border-right: .1em solid $orange-400; - } -} - -.variant-quantity-display, .variant-remaining-stock { - font-size: 0.875em; - margin-top: 0.25em; - text-align: center; - width: 7rem; - display: inline-block; -} - -.variant-quantity-display { - visibility: hidden; - - &.visible { - visibility: visible; - } -} - -.variant-remaining-stock { - color: $red-500; -} +@import "shop_partials/shop-inputs"; button.bulk-buy.variant-quantity { background-color: transparent; diff --git a/app/webpacker/css/darkswarm/_shop-product-rows.scss b/app/webpacker/css/darkswarm/_shop-product-rows.scss index 0ab0a8b1fd7..ddf25d6d51a 100644 --- a/app/webpacker/css/darkswarm/_shop-product-rows.scss +++ b/app/webpacker/css/darkswarm/_shop-product-rows.scss @@ -1,201 +1,7 @@ .darkswarm { products { product { - // GENERAL LAYOUT - .row { - .columns { - padding-top: 0em; - padding-bottom: 0em; - line-height: 1.1; - } - } - - .shop-variants { - // product-thumb width + 1rem - padding-left: calc(22.222% + 1rem); - - @include breakpoint(phablet) { - padding-left: 0; - clear: left; - } - } - - // ROW VARIANTS - .row.variants { - margin: 0 0 1em 0; - - &.out-of-stock { - opacity: 0.2; - } - - .variant-name, - .total-price { - padding-top: .74em; - } - .variant-price { - padding-top: .65em; - } - - // Variant name - .variant-name { - padding-left: 0; - padding-right: 0; - - @include breakpoint(phablet) { - padding-left: 0.5rem; - } - - & > *:nth-child(n + 2) { - color: $grey-550; - font-size: 0.875rem; - font-style: italic; - line-height: normal; - } - } - - // Variant price - .variant-price { - white-space: nowrap; - @include breakpoint(phablet) { - padding-left: 1rem; - } - } - - .variant-unit-price { - color: $grey-700; - font-size: 0.85rem; - margin-top: 15px; - position: relative; - left: -1px; - } - - // Total price - .total-price { - padding-left: 0rem; - color: $disabled-med; - - .filled { - color: $med-drk-grey; - } - - @include breakpoint(phablet) { - display: none; - } - } - } - - // ROW SUMMARY - .summary { - margin-left: 0; - margin-right: 0; - margin-bottom: 1.25em; - background: #fff; - - .columns { - padding-top: 1em; - padding-bottom: 1em; - line-height: 1; - - @include breakpoint(tablet) { - padding-top: 0.65rem; - padding-bottom: 0.65rem; - } - } - - .summary-header { - // product-thumb width + 1rem - padding-left: calc(22.222% + 1rem); - padding-right: 1rem; - - @include breakpoint(phablet) { - padding-left: calc(33.333% + 1rem); - } - - .product-producer { - color: $grey-550; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-style: italic; - - a { - color: $teal-500; - - &:hover, &:focus, &:active { - color: $teal-600; - text-decoration: underline; - } - } - } - - h3 { - font-size: 1.3rem; - margin-top: 0.75rem; - margin-bottom: 0.6rem; - } - - h3 a { - color: $orange-500; - - &:hover, &:focus, &:active { - color: $orange-600; - text-decoration: underline; - } - } - - .product-description { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-bottom: 0.75rem; - cursor: pointer; - // Force product description to be on one line - // and truncate with ellipsis - display: -webkit-box; - -webkit-line-clamp: 1; - -webkit-box-orient: vertical; - overflow: hidden; - // line-clamp is not supported in Safari - // Trick to get overflow: hidden to work in old Safari - line-height: 1rem; - height: 1.75rem; - - > div { - margin-bottom: 1.5rem; // Equivalent to p (trix doesn't use p as separator by default, so emulate div as p to be backward compatible) - } - - @include trix-styles; - } - - .product-properties { - margin: .5em 0; - - li { - margin: 0 0.25rem 0.25rem 0; - - a { - padding: 0.1em 0.625em; - - cursor: auto; - - &.has-tip { - cursor: pointer; - font-weight: normal; - } - &:hover, &:focus { - border-color: #ccc; - } - } - - // Foundation doesn't show the nub on mobile. - // Repeating the style to show it here. - .nub { - border-color: transparent transparent #333333 transparent; - } - } - } - } - } + @import "shop_partials/shop-product-rows"; } } } diff --git a/app/webpacker/css/darkswarm/_shop-product-thumb.scss b/app/webpacker/css/darkswarm/_shop-product-thumb.scss index 9b51d7f881f..d218a9c1ada 100644 --- a/app/webpacker/css/darkswarm/_shop-product-thumb.scss +++ b/app/webpacker/css/darkswarm/_shop-product-thumb.scss @@ -1,34 +1,7 @@ .darkswarm { products { product { - .product-thumb { - // Desktop: the product summary is nine columns wide. Use two - // for the image. 100% / 9 * 2 = 22.222% <= 192px - width: calc(22.222%); - float: left; - - // Mobile: the summary has full twelve columns and the image - // should take four of them. 100% / 12 * 4 = 33.333% <= 227px - @include breakpoint(phablet) { - width: calc(33.333%); - } - - // Make this an anchor for the bulk label. - position: relative; - - .product-thumb__bulk-label { - background-color: $grey-700; - color: white; - position: absolute; - right: 0; - top: .8em; - padding: .25em .5em; - } - - &:hover { - filter: brightness(96%); - } - } + @import "shop_partials/shop-product-thumb"; } } } diff --git a/app/webpacker/css/darkswarm/shop_partials/_shop-inputs.scss b/app/webpacker/css/darkswarm/shop_partials/_shop-inputs.scss new file mode 100644 index 00000000000..65dbfaca773 --- /dev/null +++ b/app/webpacker/css/darkswarm/shop_partials/_shop-inputs.scss @@ -0,0 +1,76 @@ +.variant-quantity-inputs { + height: 2.5rem; + white-space: nowrap; +} + +button.add-variant, button.variant-quantity { + height: 2.5rem; + border-radius: 0; + background-color: $orange-500; + color: white; + // Override foundation button styles: + font-size: 1rem; + margin: 0; + padding: 0; + transition: none; + + &:hover { + background-color: $orange-600; + } + + &[disabled] { + background-color: $grey-400; + + &:hover, &:focus { + background-color: $grey-400; + } + } + &:nth-of-type(1) { + border-bottom-left-radius: 0.25em; + border-top-left-radius: 0.25em; + } + &:nth-last-of-type(1) { + border-top-right-radius: 0.25em; + border-bottom-right-radius: 0.25em; + } +} + +button.add-variant { + min-width: 7rem; + padding: 0 1em; + + &[disabled] { + &:hover, &:focus { + background-color: $orange-500; + } + } +} + +button.variant-quantity { + width: 2.25rem; + + &:nth-of-type(1):not(.bulk-buy):not(.bulk-buy-add) { + border-right: .1em solid $orange-400; + } +} + +.variant-quantity-display, .variant-remaining-stock { + font-size: 0.875em; + margin-top: 0.25em; + text-align: center; + width: 7rem; + display: inline-block; +} + +.variant-quantity-display { + visibility: hidden; + + &.visible { + visibility: visible; + } +} + +.variant-remaining-stock { + color: $red-500; +} + diff --git a/app/webpacker/css/darkswarm/shop_partials/_shop-product-rows.scss b/app/webpacker/css/darkswarm/shop_partials/_shop-product-rows.scss new file mode 100644 index 00000000000..1cf62b4263b --- /dev/null +++ b/app/webpacker/css/darkswarm/shop_partials/_shop-product-rows.scss @@ -0,0 +1,195 @@ +// GENERAL LAYOUT +.row { + .columns { + padding-top: 0em; + padding-bottom: 0em; + line-height: 1.1; + } +} + +.shop-variants { + // product-thumb width + 1rem + padding-left: calc(22.222% + 1rem); + + @include breakpoint(phablet) { + padding-left: 0; + clear: left; + } +} + +// ROW VARIANTS +.row.variants { + margin: 0 0 1em 0; + + &.out-of-stock { + opacity: 0.2; + } + + .variant-name, + .total-price { + padding-top: .74em; + } + .variant-price { + padding-top: .65em; + } + + // Variant name + .variant-name { + padding-left: 0; + padding-right: 0; + + @include breakpoint(phablet) { + padding-left: 0.5rem; + } + + & > *:nth-child(n + 2) { + color: $grey-550; + font-size: 0.875rem; + font-style: italic; + line-height: normal; + } + } + + // Variant price + .variant-price { + white-space: nowrap; + @include breakpoint(phablet) { + padding-left: 1rem; + } + } + + .variant-unit-price { + color: $grey-700; + font-size: 0.85rem; + margin-top: 15px; + position: relative; + left: -1px; + } + + // Total price + .total-price { + padding-left: 0rem; + color: $disabled-med; + + .filled { + color: $med-drk-grey; + } + + @include breakpoint(phablet) { + display: none; + } + } +} + +// ROW SUMMARY +.summary { + margin-left: 0; + margin-right: 0; + margin-bottom: 1.25em; + background: #fff; + + .columns { + padding-top: 1em; + padding-bottom: 1em; + line-height: 1; + + @include breakpoint(tablet) { + padding-top: 0.65rem; + padding-bottom: 0.65rem; + } + } + + .summary-header { + // product-thumb width + 1rem + padding-left: calc(22.222% + 1rem); + padding-right: 1rem; + + @include breakpoint(phablet) { + padding-left: calc(33.333% + 1rem); + } + + .product-producer { + color: $grey-550; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-style: italic; + + a { + color: $teal-500; + + &:hover, &:focus, &:active { + color: $teal-600; + text-decoration: underline; + } + } + } + + h3 { + font-size: 1.3rem; + margin-top: 0.75rem; + margin-bottom: 0.6rem; + } + + h3 a { + color: $orange-500; + + &:hover, &:focus, &:active { + color: $orange-600; + text-decoration: underline; + } + } + + .product-description { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 0.75rem; + cursor: pointer; + // Force product description to be on one line + // and truncate with ellipsis + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + overflow: hidden; + // line-clamp is not supported in Safari + // Trick to get overflow: hidden to work in old Safari + line-height: 1rem; + height: 1.75rem; + + > div { + margin-bottom: 1.5rem; // Equivalent to p (trix doesn't use p as separator by default, so emulate div as p to be backward compatible) + } + + @include trix-styles; + } + + .product-properties { + margin: .5em 0; + + li { + margin: 0 0.25rem 0.25rem 0; + + a { + padding: 0.1em 0.625em; + + cursor: auto; + + &.has-tip { + cursor: pointer; + font-weight: normal; + } + &:hover, &:focus { + border-color: #ccc; + } + } + + // Foundation doesn't show the nub on mobile. + // Repeating the style to show it here. + .nub { + border-color: transparent transparent #333333 transparent; + } + } + } + } +} diff --git a/app/webpacker/css/darkswarm/shop_partials/_shop-product-thumb.scss b/app/webpacker/css/darkswarm/shop_partials/_shop-product-thumb.scss new file mode 100644 index 00000000000..eb88dd74056 --- /dev/null +++ b/app/webpacker/css/darkswarm/shop_partials/_shop-product-thumb.scss @@ -0,0 +1,28 @@ +.product-thumb { + // Desktop: the product summary is nine columns wide. Use two + // for the image. 100% / 9 * 2 = 22.222% <= 192px + width: calc(22.222%); + float: left; + + // Mobile: the summary has full twelve columns and the image + // should take four of them. 100% / 12 * 4 = 33.333% <= 227px + @include breakpoint(phablet) { + width: calc(33.333%); + } + + // Make this an anchor for the bulk label. + position: relative; + + .product-thumb__bulk-label { + background-color: $grey-700; + color: white; + position: absolute; + right: 0; + top: .8em; + padding: .25em .5em; + } + + &:hover { + filter: brightness(96%); + } +} diff --git a/app/webpacker/css/shared/question-mark-icon.scss b/app/webpacker/css/shared/question-mark-icon.scss index e8720d207c2..932c8978447 100644 --- a/app/webpacker/css/shared/question-mark-icon.scss +++ b/app/webpacker/css/shared/question-mark-icon.scss @@ -50,8 +50,19 @@ margin-left: 2px; } +@mixin joyride-content { + background-color: $dynamic-blue; + padding: $padding-small; + border-radius: $radius-small; + color: $white; + width: 100%; + font-size: 0.8rem; +} + +$joyride-width: 16rem; + .joyride-tip-guide.question-mark-tooltip { - width: 16rem; + width: $joyride-width; max-width: 65%; // JS needs to be tweaked to adjust for left alignment - this is dynamic can't rewrite in CSS margin-left: -7.4rem; @@ -70,12 +81,7 @@ } .joyride-content-wrapper { - background-color: $dynamic-blue; - padding: $padding-small; - border-radius: $radius-small; - color: $white; - width: 100%; - font-size: 0.8rem; + @include joyride-content; } .joyride-nub.bottom { From b6695ba9a290d09b6c10ad3b742023b1169b10bf Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Wed, 4 Sep 2024 21:02:20 +1000 Subject: [PATCH 13/26] Add product preview on product edit page Plus translation --- app/views/admin/products_v3/_product_row.html.haml | 2 +- app/views/spree/admin/products/edit.html.haml | 8 +++++++- config/locales/en.yml | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/views/admin/products_v3/_product_row.html.haml b/app/views/admin/products_v3/_product_row.html.haml index 5fda0094795..edd9d406b67 100644 --- a/app/views/admin/products_v3/_product_row.html.haml +++ b/app/views/admin/products_v3/_product_row.html.haml @@ -40,4 +40,4 @@ "data-modal-link-target-value": "product-delete-modal", "class": "delete", "data-modal-link-modal-dataset-value": {'data-delete-path': admin_product_destroy_path(product)}.to_json } = t('admin.products_page.actions.delete') - = link_to "Preview", admin_product_preview_path(product), {"data-turbo-stream": "" } + = link_to t('admin.products_page.actions.preview'), admin_product_preview_path(product), {"data-turbo-stream": "" } diff --git a/app/views/spree/admin/products/edit.html.haml b/app/views/spree/admin/products/edit.html.haml index 8591ffe2117..8fa74e62c46 100644 --- a/app/views/spree/admin/products/edit.html.haml +++ b/app/views/spree/admin/products/edit.html.haml @@ -11,9 +11,15 @@ = render :partial => 'spree/shared/error_messages', :locals => { :target => @product } = form_for [:admin, @product], :url => admin_product_path(@product, @url_filters), :method => :put, :html => { :multipart => true } do |f| - %fieldset.no-border-top{'ng-app' => 'admin.products'} + %fieldset.no-border-top{'ng-app': 'admin.products', 'data-turbo': true, 'data-controller': "product-preview"} = render :partial => 'form', :locals => { :f => f } .form-buttons.filter-actions.actions + = link_to t("admin.products_page.actions.preview"), Rails.application.routes.url_helpers.admin_product_preview_path(@product), {"data-turbo-stream": "" , class: "button secondary"} + = button t(:update), 'icon-refresh' = button_link_to t(:cancel), products_return_to_url(@url_filters), icon: 'icon-remove' + + = render ModalComponent.new(id: "product-preview-modal", modal_class: "big", "data-action": "product-preview:open@window->modal#open") do + #product-preview + diff --git a/config/locales/en.yml b/config/locales/en.yml index 00465679017..f1041c92bbb 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -604,6 +604,7 @@ en: clone: Clone delete: Delete remove: Remove + preview: Preview image: edit: Edit product_preview: From 8de7c304fea32f7f298ffd618ddcc46efe00898c Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Wed, 4 Sep 2024 22:11:39 +1000 Subject: [PATCH 14/26] Add AdminTooltipComponent I left the stimulus controller separated as it is generic enough --- app/components/admin_tooltip_component.rb | 11 ++++ .../admin_tooltip_component.html.haml | 8 +++ .../product_preview.turbo_stream.haml | 6 +-- app/views/admin/shared/_tooltip.html.haml | 10 ---- .../shared/_whats_this_tooltip.html.haml | 2 +- .../spree/admin/orders/_table_row.html.haml | 2 +- .../admin_tooltip_component_spec.rb | 54 +++++++++++++++++++ 7 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 app/components/admin_tooltip_component.rb create mode 100644 app/components/admin_tooltip_component/admin_tooltip_component.html.haml delete mode 100644 app/views/admin/shared/_tooltip.html.haml create mode 100644 spec/components/admin_tooltip_component_spec.rb diff --git a/app/components/admin_tooltip_component.rb b/app/components/admin_tooltip_component.rb new file mode 100644 index 00000000000..f790fffd1d4 --- /dev/null +++ b/app/components/admin_tooltip_component.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AdminTooltipComponent < ViewComponent::Base + def initialize(text:, link_text:, placement: "top", link: "", link_class: "") + @text = text + @link_text = link_text + @placement = placement + @link = link + @link_class = link_class + end +end diff --git a/app/components/admin_tooltip_component/admin_tooltip_component.html.haml b/app/components/admin_tooltip_component/admin_tooltip_component.html.haml new file mode 100644 index 00000000000..d4e76859405 --- /dev/null +++ b/app/components/admin_tooltip_component/admin_tooltip_component.html.haml @@ -0,0 +1,8 @@ +%div{"data-controller": "tooltip", "data-tooltip-placement-value": @placement } + %a{"data-tooltip-target": "element", href: @link, class: @link_class} + = @link_text + .tooltip-container + .tooltip{"data-tooltip-target": "tooltip"} + = sanitize @text + .arrow{"data-tooltip-target": "arrow"} + diff --git a/app/views/admin/products_v3/product_preview.turbo_stream.haml b/app/views/admin/products_v3/product_preview.turbo_stream.haml index ca4e62b535d..ded87d134ff 100644 --- a/app/views/admin/products_v3/product_preview.turbo_stream.haml +++ b/app/views/admin/products_v3/product_preview.turbo_stream.haml @@ -43,7 +43,7 @@ - @product.properties_including_inherited.each do |property| %li - if property[:value].present? - = render partial: "admin/shared/tooltip", locals: { tooltip_text: property[:value], link_text: property[:name], placement: "bottom" } + = render AdminTooltipComponent.new(text: property[:value], link_text: property[:name], placement: "bottom") - else %a %span @@ -60,7 +60,7 @@ .small-4.medium-3.columns.variant-price = number_to_currency(variant.price) .unit-price.variant-unit-price - = render partial: "admin/shared/tooltip", locals: { tooltip_text: t("js.shopfront.unit_price_tooltip"), link_text: "", link: "", link_class: "question-mark-icon", placement: "top" } + = render AdminTooltipComponent.new(text: t("js.shopfront.unit_price_tooltip"), link_text: "", placement: "top", link_class: "question-mark-icon") - # TODO use an helper - unit_price = UnitPrice.new(variant) - price_per_unit = variant.price / (unit_price.denominator || 1) @@ -98,7 +98,7 @@ - @product.properties_including_inherited.each do |property| %li - if property[:value].present? - = render partial: "admin/shared/tooltip", locals: { tooltip_text: property[:value], link_text: property[:name], placement: "bottom" } + = render AdminTooltipComponent.new(text: property[:value], link_text: property[:name], placement: "bottom") - else %a %span diff --git a/app/views/admin/shared/_tooltip.html.haml b/app/views/admin/shared/_tooltip.html.haml deleted file mode 100644 index ca6f98310b6..00000000000 --- a/app/views/admin/shared/_tooltip.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -- tooltip_placement = defined?(placement) ? placement : "top" -- tooltip_link = defined?(link) ? link : "" -- tooltip_link_class = defined?(link_class) ? link_class : "" -%div{"data-controller": "tooltip", "data-tooltip-placement-value": tooltip_placement } - %a{"data-tooltip-target": "element", href: tooltip_link, class: tooltip_link_class} - = link_text - .tooltip-container - .tooltip{"data-tooltip-target": "tooltip"} - = sanitize tooltip_text - .arrow{"data-tooltip-target": "arrow"} diff --git a/app/views/admin/shared/_whats_this_tooltip.html.haml b/app/views/admin/shared/_whats_this_tooltip.html.haml index d63fa417685..73d4a21d514 100644 --- a/app/views/admin/shared/_whats_this_tooltip.html.haml +++ b/app/views/admin/shared/_whats_this_tooltip.html.haml @@ -1 +1 @@ -= render partial: 'admin/shared/tooltip', locals: {link_class: "" ,link: nil, link_text: t('admin.whats_this'), tooltip_text: tooltip_text} += render AdminTooltipComponent.new(text: tooltip_text, link_text: t('admin.whats_this'), link: nil) diff --git a/app/views/spree/admin/orders/_table_row.html.haml b/app/views/spree/admin/orders/_table_row.html.haml index cb0b5793916..7cc4aae10ff 100644 --- a/app/views/spree/admin/orders/_table_row.html.haml +++ b/app/views/spree/admin/orders/_table_row.html.haml @@ -45,7 +45,7 @@ %div.row-loading-icons - if local_assigns[:success] %i.success.icon-ok-sign{"data-controller": "ephemeral"} - = render partial: 'admin/shared/tooltip', locals: {link_class: "icon_link with-tip icon-edit no-text" ,link: edit_admin_order_path(order), link_text: "", tooltip_text: t('spree.admin.orders.index.edit')} + = 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) diff --git a/spec/components/admin_tooltip_component_spec.rb b/spec/components/admin_tooltip_component_spec.rb new file mode 100644 index 00000000000..4e0039dcead --- /dev/null +++ b/spec/components/admin_tooltip_component_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe AdminTooltipComponent, type: :component do + it "displays the tooltip link" do + render_inline(described_class.new(text: "Tooltip description", link_text: "Hover here")) + + expect(page).to have_selector "a", text: "Hover here" + end + + describe "text" do + it "displays the tooltip text" do + render_inline(described_class.new(text: "Tooltip description", link_text: "Hover here")) + + expect(page).to have_selector ".tooltip", text: "Tooltip description" + end + + it "sanitizes the tooltip text" do + render_inline(described_class.new( text: "Tooltip description", + link_text: "Hover here")) + + expect(page).to have_selector ".tooltip", text: "Tooltip description" + end + end + + describe "placement" do + it "uses top as default" do + render_inline(described_class.new(text: "Tooltip description", + link_text: "Hover here")) + + expect(page).to have_selector '[data-tooltip-placement-value="top"]' + end + + it "uses the given placement" do + render_inline(described_class.new(text: "Tooltip description", + link_text: "Hover here", placement: "left")) + expect(page).to have_selector '[data-tooltip-placement-value="left"]' + end + end + + it "adds the correct link" do + render_inline(described_class.new(text: "Tooltip description", link_text: "Hover here", + link: "www.ofn.com")) + + expect(page).to have_selector '[href="www.ofn.com"]' + end + + it "adds the correct link_class" do + render_inline(described_class.new(text: "Tooltip description", link_text: "Hover here", + link_class: "pretty")) + expect(page).to have_selector 'a[class="pretty"]' + end +end From 5bf6bdf7f07b8d5f0239a2ad8fd8f9ab6281ecf7 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Mon, 9 Sep 2024 15:27:59 +1000 Subject: [PATCH 15/26] Fix some display issue with long description --- .../product_preview.turbo_stream.haml | 5 ++--- .../css/admin_v3/pages/product_preview.scss | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/views/admin/products_v3/product_preview.turbo_stream.haml b/app/views/admin/products_v3/product_preview.turbo_stream.haml index ded87d134ff..7ea9ce456c8 100644 --- a/app/views/admin/products_v3/product_preview.turbo_stream.haml +++ b/app/views/admin/products_v3/product_preview.turbo_stream.haml @@ -106,9 +106,8 @@ - if @product.description - .product-description - - # TODO description not wrapped properly - %p.text-small{ 'data-controller': "add-blank-to-link" } + .product-description{ 'data-controller': "add-blank-to-link" } + %p.text-small - # description is sanitized in Spree::Product#description method = @product.description.html_safe diff --git a/app/webpacker/css/admin_v3/pages/product_preview.scss b/app/webpacker/css/admin_v3/pages/product_preview.scss index 90fa84c80d2..fdf47f90957 100644 --- a/app/webpacker/css/admin_v3/pages/product_preview.scss +++ b/app/webpacker/css/admin_v3/pages/product_preview.scss @@ -121,6 +121,15 @@ } // from foundation-sites/scss/foundations/components/_grid.scss + @media only screen and (min-width: 64.0625em) { + .column, .columns { + position: relative; + padding-left: 0.9375rem; + padding-right: 0.9375rem; + float: left; + } + } + .column + .column:last-child, .column + .columns:last-child, .columns + .column:last-child, @@ -144,6 +153,12 @@ } } + @media only screen and (min-width: 64.0625em) { + .large-6 { + width: 50%; + } + } + // from foundation-sites/scss/foundations/components/_global.scss img { display: inline-block; @@ -187,9 +202,14 @@ .tooltip { @include joyride-content; width: $joyride-width; + text-transform: none; } .arrow { background-color: $dynamic-blue; } + + .columns { + margin-left: 0; + } } From 379e5acfe5e7a81abac22dd1b5c1aadd3d42b858 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Tue, 10 Sep 2024 10:23:22 +1000 Subject: [PATCH 16/26] Fix product preview modal opening The previous solution failed to take into account that it would have been trigger on any turbo steam rendering action, not just the product preview one. Now the open event is dispatched when the product preview controller is connected, which happens when the modal html is rendered. --- app/views/admin/products_v3/_table.html.haml | 2 +- .../admin/products_v3/product_preview.turbo_stream.haml | 2 +- app/webpacker/controllers/product_preview_controller.js | 9 ++------- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/app/views/admin/products_v3/_table.html.haml b/app/views/admin/products_v3/_table.html.haml index 40a6858f342..b45eabab72a 100644 --- a/app/views/admin/products_v3/_table.html.haml +++ b/app/views/admin/products_v3/_table.html.haml @@ -13,7 +13,7 @@ = hidden_field_tag :producer_id, @producer_id = hidden_field_tag :category_id, @category_id - %table.products{ 'data-column-preferences-target': "table", 'data-controller': "product-preview" } + %table.products{ 'data-column-preferences-target': "table" } %colgroup -# The `min-width` property works in Chrome but not Firefox so is considered progressive enhancement. %col.col-image{ width:"44px" }= # (image size + padding) diff --git a/app/views/admin/products_v3/product_preview.turbo_stream.haml b/app/views/admin/products_v3/product_preview.turbo_stream.haml index 7ea9ce456c8..ce1cdefbbc2 100644 --- a/app/views/admin/products_v3/product_preview.turbo_stream.haml +++ b/app/views/admin/products_v3/product_preview.turbo_stream.haml @@ -1,5 +1,5 @@ = turbo_stream.replace "product-preview" do - #product-preview{ "data-controller": "tabs" } + #product-preview{ "data-controller": "tabs product-preview" } %h1 = t("admin.products_page.product_preview.product_preview") %dl.tabs diff --git a/app/webpacker/controllers/product_preview_controller.js b/app/webpacker/controllers/product_preview_controller.js index cbb1551f014..6b251c540a4 100644 --- a/app/webpacker/controllers/product_preview_controller.js +++ b/app/webpacker/controllers/product_preview_controller.js @@ -2,13 +2,8 @@ import { Controller } from "stimulus"; export default class extends Controller { connect() { - // open the modal before rendering the html to avoid opening a blank modal - // TODO other option is having a controller in the stream html that would dispatch the open element on connect - window.addEventListener("turbo:before-stream-render", this.#open.bind(this)); - } - - disconnect() { - window.removeEventListener("turbo:before-stream-render", this.#open.bind(this)); + // open the modal when html is rendering to avoid opening a blank modal + this.#open() } // private From 69937507578ca91bf8d7568bc29df89bc3eb3e30 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Tue, 10 Sep 2024 10:44:35 +1000 Subject: [PATCH 17/26] Fix product v3 action system spec --- spec/system/admin/products_v3/actions_spec.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/spec/system/admin/products_v3/actions_spec.rb b/spec/system/admin/products_v3/actions_spec.rb index e864218c1d7..f39cb82aa40 100644 --- a/spec/system/admin/products_v3/actions_spec.rb +++ b/spec/system/admin/products_v3/actions_spec.rb @@ -202,6 +202,7 @@ def expect_other_columns_visible page.find(".vertical-ellipsis-menu").click expect(page).to have_link "Edit", href: spree.edit_admin_product_path(product_a) end + close_action_menu within row_containing_name("Medium box") do page.find(".vertical-ellipsis-menu").click @@ -232,6 +233,7 @@ def expect_other_columns_visible page.find(".vertical-ellipsis-menu").click expect(page).to have_link "Clone", href: admin_clone_product_path(product_a) end + close_action_menu within row_containing_name("Medium box") do page.find(".vertical-ellipsis-menu").click @@ -351,11 +353,12 @@ def expect_other_columns_visible page.find(".vertical-ellipsis-menu").click page.find(delete_option_selector).click end + within modal_selector do click_button "Keep product" end - expect(page).not_to have_selector(modal_selector) + expect(page).not_to have_content "Delete Product" expect(page).to have_selector(product_selector) # Keep Variant @@ -367,7 +370,7 @@ def expect_other_columns_visible click_button "Keep variant" end - expect(page).not_to have_selector(modal_selector) + expect(page).not_to have_content("Delete Variant") expect(page).to have_selector(variant_selector) end end @@ -387,7 +390,7 @@ def expect_other_columns_visible click_button "Delete variant" end - expect(page).not_to have_selector(modal_selector) + expect(page).not_to have_content("Delete variant") expect(page).not_to have_selector(variant_selector) within success_flash_message_selector do expect(page).to have_content("Successfully deleted the variant") @@ -402,7 +405,7 @@ def expect_other_columns_visible within modal_selector do click_button "Delete product" end - expect(page).not_to have_selector(modal_selector) + expect(page).not_to have_content("Delete product") expect(page).not_to have_selector(product_selector) within success_flash_message_selector do expect(page).to have_content("Successfully deleted the product") @@ -557,4 +560,8 @@ def expect_other_columns_visible expect(page).to have_selector row_containing_name("Pommes") end end + + def close_action_menu + page.find("div#content").click + end end From 908caa984b3b233249d9b0e037af014f4122adb5 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Tue, 10 Sep 2024 11:50:17 +1000 Subject: [PATCH 18/26] Add system spec for product preview --- spec/system/admin/products_v3/actions_spec.rb | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/spec/system/admin/products_v3/actions_spec.rb b/spec/system/admin/products_v3/actions_spec.rb index f39cb82aa40..f22d7c3b145 100644 --- a/spec/system/admin/products_v3/actions_spec.rb +++ b/spec/system/admin/products_v3/actions_spec.rb @@ -475,6 +475,39 @@ def expect_other_columns_visible end end end + + describe "Preview" do + let(:product) { create(:product, name: "Apples") } + let!(:variant) { create(:variant, product:) } + + it "show product preview modal" do + login_as_admin + visit admin_products_url + + within row_containing_name("Apples") do + open_action_menu + click_link "Preview" + end + + expect(page).to have_content("Product preview") + + within "#product-preview-modal" do + # Shop tab + expect(page).to have_selector("h3 a span", text: "Apples") + add_buttons = page.all(".add-variant") + expect(add_buttons.length).to eql(2) + + # Product Details tab + find("a", text: "Product details").click # click_link doesn't work + expect(page).to have_selector("h3", text: "Apples") + + # Closing the modal + click_button "Close" + end + + expect(page).not_to have_content("Product preview") + end + end end context "as an enterprise manager" do @@ -561,6 +594,10 @@ def expect_other_columns_visible end end + def open_action_menu + page.find(".vertical-ellipsis-menu").click + end + def close_action_menu page.find("div#content").click end From 00768f6ba0d2f9637cc1e4a2d26fe0a5c9314c7f Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Tue, 10 Sep 2024 13:03:52 +1000 Subject: [PATCH 19/26] Add sytem spec for product preview on product edit page --- spec/system/admin/products_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/system/admin/products_spec.rb b/spec/system/admin/products_spec.rb index 0691982e222..325d48677b8 100644 --- a/spec/system/admin/products_spec.rb +++ b/spec/system/admin/products_spec.rb @@ -347,13 +347,28 @@ end it "editing a product" do + login_as_admin visit spree.edit_admin_product_path product fill_in_trix_editor 'product_description', with: 'A description...' + click_button 'Update' + expect(flash_message).to eq('Product "a product" has been successfully updated!') product.reload expect(product.description).to eq("
A description...
") + + # Product preview + click_link 'Preview' + + within "#product-preview-modal" do + expect(page).to have_content("Product preview") + expect(page).to have_selector("h3 a span", text: "a product") + + click_button "Close" + end + + expect(page).not_to have_content("Product preview") end it "editing product group buy options" do From 7e84d41e8ca3dd1cb6bfde52014a807b4867892d Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Wed, 11 Sep 2024 14:20:41 +1000 Subject: [PATCH 20/26] Simplify modal opening by just rendering the modal in turbo stream --- app/views/admin/products_v3/index.html.haml | 4 +- .../product_preview.turbo_stream.haml | 205 +++++++++--------- app/views/spree/admin/products/edit.html.haml | 4 +- .../controllers/product_preview_controller.js | 16 -- 4 files changed, 105 insertions(+), 124 deletions(-) delete mode 100644 app/webpacker/controllers/product_preview_controller.js diff --git a/app/views/admin/products_v3/index.html.haml b/app/views/admin/products_v3/index.html.haml index 4e349481231..fed068a609f 100644 --- a/app/views/admin/products_v3/index.html.haml +++ b/app/views/admin/products_v3/index.html.haml @@ -20,6 +20,4 @@ = render partial: 'delete_modal', locals: { object_type: } #modal-component #edit_image_modal - - = render ModalComponent.new(id: "product-preview-modal", modal_class: "big", "data-action": "product-preview:open@window->modal#open") do - #product-preview + #product-preview-modal-container diff --git a/app/views/admin/products_v3/product_preview.turbo_stream.haml b/app/views/admin/products_v3/product_preview.turbo_stream.haml index ce1cdefbbc2..060eb543867 100644 --- a/app/views/admin/products_v3/product_preview.turbo_stream.haml +++ b/app/views/admin/products_v3/product_preview.turbo_stream.haml @@ -1,43 +1,99 @@ -= turbo_stream.replace "product-preview" do - #product-preview{ "data-controller": "tabs product-preview" } - %h1 - = t("admin.products_page.product_preview.product_preview") - %dl.tabs - %dd - %a{ data: { "tabs-target": "tab", "action": "tabs#select" } } - = t("admin.products_page.product_preview.shop_tab") - %dd - %a{ data: { "tabs-target": "tab", "action": "tabs#select" } } - = t("admin.products_page.product_preview.product_details_tab") - .tabs-content - .content.active - %div{ data: { "tabs-target": "content" } } - .product-thumb - %a - - if @product.group_buy - %span.product-thumb__bulk-label - = t(".bulk") - = image_tag @product.image&.url(:small) || Spree::Image.default_image_url(:small) += turbo_stream.update "product-preview-modal-container" do + = render ModalComponent.new(id: "product-preview-modal", instant: true, modal_class: "big") do + #product-preview{ "data-controller": "tabs" } + %h1 + = t("admin.products_page.product_preview.product_preview") + %dl.tabs + %dd + %a{ data: { "tabs-target": "tab", "action": "tabs#select" } } + = t("admin.products_page.product_preview.shop_tab") + %dd + %a{ data: { "tabs-target": "tab", "action": "tabs#select" } } + = t("admin.products_page.product_preview.product_details_tab") + .tabs-content + .content.active + %div{ data: { "tabs-target": "content" } } + .product-thumb + %a + - if @product.group_buy + %span.product-thumb__bulk-label + = t(".bulk") + = image_tag @product.image&.url(:small) || Spree::Image.default_image_url(:small) + + .summary + .summary-header + %h3 + %a + %span + = @product.name + - if @product.description + .product-description{ "data-controller": "add-blank-to-link" } + - # description is sanitized in Spree::Product#description method + = @product.description.html_safe + + - if @product.variants.first.supplier.visible + %div + .product-producer + = t :products_from + %span + %a + = @product.variants.first.supplier.name + .product-properties.filter-shopfront.property-selectors + .filter-shopfront.property-selectors.inline-block + %ul + - @product.properties_including_inherited.each do |property| + %li + - if property[:value].present? + = render AdminTooltipComponent.new(text: property[:value], link_text: property[:name], placement: "bottom") + - else + %a + %span + = property[:name] + .shop-variants + - @product.variants.sort { |v1, v2| v1.name_to_display <=> v2.name_to_display }.sort { |v1, v2| v1.unit_value <=> v2.unit_value }.each do |variant| + .variants.row + .small-3.columns.variant-name + - if variant.display_name.present? + .inline + = variant.display_name + .variant-unit + = variant.unit_to_display + .small-4.medium-3.columns.variant-price + = number_to_currency(variant.price) + .unit-price.variant-unit-price + = render AdminTooltipComponent.new(text: t("js.shopfront.unit_price_tooltip"), link_text: "", placement: "top", link_class: "question-mark-icon") + - # TODO use an helper + - unit_price = UnitPrice.new(variant) + - price_per_unit = variant.price / (unit_price.denominator || 1) + = "#{number_to_currency(price_per_unit)} / #{unit_price.unit}".html_safe - .summary - .summary-header - %h3 - %a - %span - = @product.name - - if @product.description - .product-description{ "data-controller": "add-blank-to-link" } - - # description is sanitized in Spree::Product#description method - = @product.description.html_safe - - if @product.variants.first.supplier.visible - %div - .product-producer - = t :products_from + .medium-3.columns.total-price %span - %a - = @product.variants.first.supplier.name - .product-properties.filter-shopfront.property-selectors + = number_to_currency(0.00) + .small-5.medium-3.large-3.columns.variant-quantity-column.text-right + .variant-quantity-inputs + %button.add-variant + = t("js.shopfront.variant.add_to_cart") + + - # TODO can't check the shop preferrence here, display by default ? + - if !variant.on_demand && variant.on_hand <= 3 + .variant-remaining-stock + = t("js.shopfront.variant.remaining_in_stock", quantity: variant.on_hand) + + %div{ data: { "tabs-target": "content" } } + .row + .columns.small-12.medium-6.large-6.product-header + %h3 + = @product.name + %span + %em + = t("products_from") + %span + = @product.variants.first.supplier.name + + %br + .filter-shopfront.property-selectors.inline-block %ul - @product.properties_including_inherited.each do |property| @@ -48,71 +104,16 @@ %a %span = property[:name] - .shop-variants - - @product.variants.sort { |v1, v2| v1.name_to_display <=> v2.name_to_display }.sort { |v1, v2| v1.unit_value <=> v2.unit_value }.each do |variant| - .variants.row - .small-3.columns.variant-name - - if variant.display_name.present? - .inline - = variant.display_name - .variant-unit - = variant.unit_to_display - .small-4.medium-3.columns.variant-price - = number_to_currency(variant.price) - .unit-price.variant-unit-price - = render AdminTooltipComponent.new(text: t("js.shopfront.unit_price_tooltip"), link_text: "", placement: "top", link_class: "question-mark-icon") - - # TODO use an helper - - unit_price = UnitPrice.new(variant) - - price_per_unit = variant.price / (unit_price.denominator || 1) - = "#{number_to_currency(price_per_unit)} / #{unit_price.unit}".html_safe - - - .medium-3.columns.total-price - %span - = number_to_currency(0.00) - .small-5.medium-3.large-3.columns.variant-quantity-column.text-right - .variant-quantity-inputs - %button.add-variant - = t("js.shopfront.variant.add_to_cart") - - - # TODO can't check the shop preferrence here, display by default ? - - if !variant.on_demand && variant.on_hand <= 3 - .variant-remaining-stock - = t("js.shopfront.variant.remaining_in_stock", quantity: variant.on_hand) - %div{ data: { "tabs-target": "content" } } - .row - .columns.small-12.medium-6.large-6.product-header - %h3 - = @product.name - %span - %em - = t("products_from") - %span - = @product.variants.first.supplier.name - %br - - .filter-shopfront.property-selectors.inline-block - %ul - - @product.properties_including_inherited.each do |property| - %li - - if property[:value].present? - = render AdminTooltipComponent.new(text: property[:value], link_text: property[:name], placement: "bottom") - - else - %a - %span - = property[:name] - - - - if @product.description - .product-description{ 'data-controller': "add-blank-to-link" } - %p.text-small - - # description is sanitized in Spree::Product#description method - = @product.description.html_safe + - if @product.description + .product-description{ 'data-controller': "add-blank-to-link" } + %p.text-small + - # description is sanitized in Spree::Product#description method + = @product.description.html_safe - .columns.small-12.medium-6.large-6.product-img - - if @product.image - %img{ src: @product.image.url(:large) } - -else - %img.placeholder{ src: Spree::Image.default_image_url(:large) } + .columns.small-12.medium-6.large-6.product-img + - if @product.image + %img{ src: @product.image.url(:large) } + -else + %img.placeholder{ src: Spree::Image.default_image_url(:large) } diff --git a/app/views/spree/admin/products/edit.html.haml b/app/views/spree/admin/products/edit.html.haml index 8fa74e62c46..003a35eee64 100644 --- a/app/views/spree/admin/products/edit.html.haml +++ b/app/views/spree/admin/products/edit.html.haml @@ -20,6 +20,4 @@ = button_link_to t(:cancel), products_return_to_url(@url_filters), icon: 'icon-remove' - = render ModalComponent.new(id: "product-preview-modal", modal_class: "big", "data-action": "product-preview:open@window->modal#open") do - #product-preview - + #product-preview-modal-container diff --git a/app/webpacker/controllers/product_preview_controller.js b/app/webpacker/controllers/product_preview_controller.js deleted file mode 100644 index 6b251c540a4..00000000000 --- a/app/webpacker/controllers/product_preview_controller.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Controller } from "stimulus"; - -export default class extends Controller { - connect() { - // open the modal when html is rendering to avoid opening a blank modal - this.#open() - } - - // private - - #open() { - // dispatch "product-preview:open" event to trigger modal->open action - // see views/admin/product_v3/index.html.haml - this.dispatch("open"); - } -} From 776b9fcdabaa750ba9b61e6ad66cdd87aaeab4a2 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Wed, 11 Sep 2024 14:24:24 +1000 Subject: [PATCH 21/26] Re enable images partial import --- app/webpacker/css/darkswarm/images.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/webpacker/css/darkswarm/images.scss b/app/webpacker/css/darkswarm/images.scss index 72780d5a075..5ac895be9ee 100644 --- a/app/webpacker/css/darkswarm/images.scss +++ b/app/webpacker/css/darkswarm/images.scss @@ -1,4 +1,4 @@ -//@import "shop_partials/images"; +@import "shop_partials/images"; .hero-img { outline: 1px solid $disabled-bright; From 67f037280a466d821b046d378ffa1683f25c17cd Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Wed, 11 Sep 2024 14:50:37 +1000 Subject: [PATCH 22/26] Add comment in shop view file It wasn't possible to directly reuse the shopfront views because they are still using angular. --- app/assets/javascripts/templates/active_selector.html.haml | 1 + app/assets/javascripts/templates/filter_selector.html.haml | 1 + app/assets/javascripts/templates/product_modal.html.haml | 1 + app/views/shop/products/_form.html.haml | 1 + app/views/shop/products/_shop_variant.html.haml | 1 + app/views/shop/products/_shop_variant_no_group_buy.html.haml | 1 + app/views/shop/products/_shop_variant_with_group_buy.html.haml | 1 + app/views/shop/products/_summary.html.haml | 1 + 8 files changed, 8 insertions(+) diff --git a/app/assets/javascripts/templates/active_selector.html.haml b/app/assets/javascripts/templates/active_selector.html.haml index c7ddfc3577c..3cfbe46f0f7 100644 --- a/app/assets/javascripts/templates/active_selector.html.haml +++ b/app/assets/javascripts/templates/active_selector.html.haml @@ -1,2 +1,3 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml %li{ "ng-class": "{active: selector.active}" } %a{ tooltip: "{{selector.object.value}}", "tooltip-placement": "bottom", "ng-transclude": true, "ng-class": "{active: selector.active, 'has-tip': selector.object.value}" } diff --git a/app/assets/javascripts/templates/filter_selector.html.haml b/app/assets/javascripts/templates/filter_selector.html.haml index 81559e4004d..c2f110a1f7b 100644 --- a/app/assets/javascripts/templates/filter_selector.html.haml +++ b/app/assets/javascripts/templates/filter_selector.html.haml @@ -1,3 +1,4 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml %ul %active-selector{ "ng-repeat": "selector in allSelectors", "ng-show": "ifDefined(selector.fits, true)" } %span{"ng-bind" => "::selector.object.name"} diff --git a/app/assets/javascripts/templates/product_modal.html.haml b/app/assets/javascripts/templates/product_modal.html.haml index 374f1bda4ed..5a85554108b 100644 --- a/app/assets/javascripts/templates/product_modal.html.haml +++ b/app/assets/javascripts/templates/product_modal.html.haml @@ -1,3 +1,4 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml .row .columns.small-12.medium-6.large-6.product-header %h3{"ng-bind" => "::product.name"} diff --git a/app/views/shop/products/_form.html.haml b/app/views/shop/products/_form.html.haml index b40ea8255ba..9d2679abc28 100644 --- a/app/views/shop/products/_form.html.haml +++ b/app/views/shop/products/_form.html.haml @@ -1,3 +1,4 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml = cache_with_locale do %form{action: main_app.cart_path} %products{"ng-init" => "refreshStaleData()", "ng-show" => "order_cycle.order_cycle_id != null", "ng-cloak" => true } diff --git a/app/views/shop/products/_shop_variant.html.haml b/app/views/shop/products/_shop_variant.html.haml index bd2c226aa82..7aa1eb9354e 100644 --- a/app/views/shop/products/_shop_variant.html.haml +++ b/app/views/shop/products/_shop_variant.html.haml @@ -1,3 +1,4 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml = cache_with_locale do .small-3.columns.variant-name .inline{"ng-if" => "::variant.display_name"} {{ ::variant.display_name }} diff --git a/app/views/shop/products/_shop_variant_no_group_buy.html.haml b/app/views/shop/products/_shop_variant_no_group_buy.html.haml index 20fbb099016..b9c7280d433 100644 --- a/app/views/shop/products/_shop_variant_no_group_buy.html.haml +++ b/app/views/shop/products/_shop_variant_no_group_buy.html.haml @@ -1,3 +1,4 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml = cache_with_locale do .small-5.medium-3.large-3.columns.variant-quantity-column.text-right{"ng-if" => "::!variant.product.group_buy"} diff --git a/app/views/shop/products/_shop_variant_with_group_buy.html.haml b/app/views/shop/products/_shop_variant_with_group_buy.html.haml index 50b4812e5c4..07e325a4055 100644 --- a/app/views/shop/products/_shop_variant_with_group_buy.html.haml +++ b/app/views/shop/products/_shop_variant_with_group_buy.html.haml @@ -1,3 +1,4 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml = cache_with_locale do .small-5.medium-3.large-3.columns.variant-quantity-column.text-right{"ng-if" => "::variant.product.group_buy"} diff --git a/app/views/shop/products/_summary.html.haml b/app/views/shop/products/_summary.html.haml index 8163d6e903f..cd3d103a0cc 100644 --- a/app/views/shop/products/_summary.html.haml +++ b/app/views/shop/products/_summary.html.haml @@ -1,3 +1,4 @@ +- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml = cache_with_locale do .product-thumb %a{"ng-click" => "triggerProductModal()"} From 3f6aaa74cc4c6e2ed6ad4bc1441a75c942e9bef9 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Fri, 13 Sep 2024 14:14:30 +1000 Subject: [PATCH 23/26] Remove duplicated styling for tabs It uses the same styling as #admin now share via mixins --- .../product_preview.turbo_stream.haml | 2 +- .../css/admin_v3/components/navigation.scss | 76 +++++++++++++------ .../css/admin_v3/pages/product_preview.scss | 43 +---------- 3 files changed, 54 insertions(+), 67 deletions(-) diff --git a/app/views/admin/products_v3/product_preview.turbo_stream.haml b/app/views/admin/products_v3/product_preview.turbo_stream.haml index 060eb543867..61935d012c1 100644 --- a/app/views/admin/products_v3/product_preview.turbo_stream.haml +++ b/app/views/admin/products_v3/product_preview.turbo_stream.haml @@ -3,7 +3,7 @@ #product-preview{ "data-controller": "tabs" } %h1 = t("admin.products_page.product_preview.product_preview") - %dl.tabs + %dl.admin-tabs %dd %a{ data: { "tabs-target": "tab", "action": "tabs#select" } } = t("admin.products_page.product_preview.shop_tab") diff --git a/app/webpacker/css/admin_v3/components/navigation.scss b/app/webpacker/css/admin_v3/components/navigation.scss index 4ef5d5a06c4..70ea3cf8b9c 100644 --- a/app/webpacker/css/admin_v3/components/navigation.scss +++ b/app/webpacker/css/admin_v3/components/navigation.scss @@ -1,11 +1,60 @@ // Navigation //--------------------------------------------------- + +@mixin menu-display { + display: flex; + flex-wrap: wrap; +} + +@mixin menu-link { + a { + display: inline-block; + padding: 16px 20px; + color: $color-9 !important; + text-align: center; + position: relative; + font-size: 14px; + font-weight: 600; + + &:hover { + color: $red !important; + + &:after { + content: ""; + position: absolute; + bottom: 0; + left: 20px; + right: 20px; + height: 3px; + background: $red; + } + } + + &.active { + @extend :hover; + } + } +} + .inline-menu { margin: 0; -webkit-margin-before: 0; -webkit-padding-start: 0; } +// tabs +/// use the same styling as #admin-menu via menu-display and menu-link mixins +dl.admin-tabs { + box-shadow: $box-shadow; + @include menu-display; + + dd { + width: auto; + padding: 0; + @include menu-link; + } +} + nav.menu { ul { list-style: none; @@ -95,33 +144,10 @@ nav.menu { } ul { - display: flex; - flex-wrap: wrap; + @include menu-display; li { - a { - display: inline-block; - padding: 16px 20px; - color: $color-9 !important; - text-align: center; - position: relative; - font-size: 14px; - font-weight: 600; - - &:hover { - color: $red !important; - - &:after { - content: ""; - position: absolute; - bottom: 0; - left: 20px; - right: 20px; - height: 3px; - background: $red; - } - } - } + @include menu-link; &.selected a { @extend a, :hover; diff --git a/app/webpacker/css/admin_v3/pages/product_preview.scss b/app/webpacker/css/admin_v3/pages/product_preview.scss index fdf47f90957..4b7f7e3a6a7 100644 --- a/app/webpacker/css/admin_v3/pages/product_preview.scss +++ b/app/webpacker/css/admin_v3/pages/product_preview.scss @@ -2,46 +2,6 @@ @import "../../darkswarm/mixins"; #product-preview { - // tabs - dl.tabs { - box-shadow: $box-shadow; - display: flex; - flex-wrap: wrap; - - dd { - width: auto; - padding: 0; - - a { - display: inline-block; - padding: 16px 20px; - color: $color-9 !important; - text-align: center; - position: relative; - font-size: 16px; - font-weight: 600; - - &:hover { - color: $red !important; - - &:after { - content: ""; - position: absolute; - bottom: 0; - left: 20px; - right: 20px; - height: 3px; - background: $red; - } - } - - &.active { - @extend a, :hover; - } - } - } - } - // The frontend css is base on foundation-sites https://github.com/foundation/foundation-sites // Below we copied the sections that are relevant to the product preview modal @@ -122,7 +82,8 @@ // from foundation-sites/scss/foundations/components/_grid.scss @media only screen and (min-width: 64.0625em) { - .column, .columns { + .column, + .columns { position: relative; padding-left: 0.9375rem; padding-right: 0.9375rem; From 38721d9f369eb2ecadb9b4ba7b5bed435a7a0610 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Fri, 13 Sep 2024 14:33:46 +1000 Subject: [PATCH 24/26] Per review, fix the tab spec Both tabs have the product name, so add check got the image on the product details tab. --- spec/system/admin/products_v3/actions_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/system/admin/products_v3/actions_spec.rb b/spec/system/admin/products_v3/actions_spec.rb index f22d7c3b145..0da32832a62 100644 --- a/spec/system/admin/products_v3/actions_spec.rb +++ b/spec/system/admin/products_v3/actions_spec.rb @@ -493,13 +493,14 @@ def expect_other_columns_visible within "#product-preview-modal" do # Shop tab - expect(page).to have_selector("h3 a span", text: "Apples") + expect(page).to have_selector("h3", text: "Apples") add_buttons = page.all(".add-variant") expect(add_buttons.length).to eql(2) # Product Details tab find("a", text: "Product details").click # click_link doesn't work expect(page).to have_selector("h3", text: "Apples") + expect(page).to have_selector(".product-img") # Closing the modal click_button "Close" From a25937321a1c808b650d0c8233241b34ee9ecb33 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Tue, 17 Sep 2024 11:46:55 +1000 Subject: [PATCH 25/26] Remove ability of any admin user to see all product And fix related spec --- app/models/spree/ability.rb | 1 - spec/models/spree/ability_spec.rb | 25 ++++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/app/models/spree/ability.rb b/app/models/spree/ability.rb index e7c050aa245..82cfb999794 100644 --- a/app/models/spree/ability.rb +++ b/app/models/spree/ability.rb @@ -29,7 +29,6 @@ def initialize(user) can :update, Order do |order, token| order.user == user || (order.token && token == order.token) end - can [:index, :read], Product can [:index, :read], ProductProperty can [:index, :read], Property can :create, Spree::User diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index f8ed7b32f6f..4bd66135f80 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -97,22 +97,15 @@ end end - context 'for Product' do - let(:resource) { Spree::Product.new } - context 'requested by any user' do - it_should_behave_like 'read only' - end - end - context 'for ProductProperty' do - let(:resource) { Spree::Product.new } + let(:resource) { Spree::ProductProperty.new } context 'requested by any user' do it_should_behave_like 'read only' end end context 'for Property' do - let(:resource) { Spree::Product.new } + let(:resource) { Spree::Property.new } context 'requested by any user' do it_should_behave_like 'read only' end @@ -359,6 +352,20 @@ is_expected.to have_ability(:create, for: Spree::Product) end + it "should be able to read/write their enterprises' products" do + is_expected.to have_ability( + [:admin, :read, :index, :update, :seo, :group_buy_options, :bulk_update, :clone, :delete, + :destroy], for: p1 + ) + end + + it "should not be able to read/write other enterprises' products" do + is_expected.not_to have_ability( + [:admin, :read, :index, :update, :seo, :group_buy_options, :bulk_update, :clone, :delete, + :destroy], for: p2 + ) + end + it "should be able to read/write their enterprises' product variants" do is_expected.to have_ability([:create], for: Spree::Variant) is_expected.to have_ability( From 40c4d38e45614aee44790d03367b7f1760e23972 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Tue, 17 Sep 2024 12:01:53 +1000 Subject: [PATCH 26/26] Add permission check --- app/controllers/admin/product_preview_controller.rb | 7 +++++++ spec/system/admin/products_v3/actions_spec.rb | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/admin/product_preview_controller.rb b/app/controllers/admin/product_preview_controller.rb index 2ea48d74fc7..8712d5497c0 100644 --- a/app/controllers/admin/product_preview_controller.rb +++ b/app/controllers/admin/product_preview_controller.rb @@ -4,6 +4,7 @@ module Admin class ProductPreviewController < Spree::Admin::BaseController def show @product = Spree::Product.find(params[:id]) + authorize! :show, @product respond_with do |format| format.turbo_stream { @@ -11,5 +12,11 @@ def show } end end + + private + + def model_class + Spree::Product + end end end diff --git a/spec/system/admin/products_v3/actions_spec.rb b/spec/system/admin/products_v3/actions_spec.rb index 0da32832a62..a4a6fc387a2 100644 --- a/spec/system/admin/products_v3/actions_spec.rb +++ b/spec/system/admin/products_v3/actions_spec.rb @@ -481,7 +481,6 @@ def expect_other_columns_visible let!(:variant) { create(:variant, product:) } it "show product preview modal" do - login_as_admin visit admin_products_url within row_containing_name("Apples") do