Popup de compra rápida

En este tutorial vamos a ver cómo agregar un popup con el formulario de producto, el cual puede ser visto desde el ítem en el listado de productos solo en computadoras.

HTML

1. Lo primero que vamos a hacer es crear las carpeta product dentro de la carpeta snipplets, y luego agregar el snipplet product-quickshop.tpl con el siguiente código:

{% embed "snipplets/modal.tpl" with{modal_id: 'quickshop-' ~ product.id ~ '', modal_position: 'bottom', modal_transition: 'slide', modal_header: true, modal_footer: true, modal_width: 'centered'  } %}
    {% block modal_head %}
        <span itemprop="name">
            {{ product.name }}
        </span>
    {% endblock %}
    {% block modal_body %}
        <div id="quick{{ product.id }}" class="js-product-container js-quickshop-container" data-variants="{{ product.variants_object | json_encode }}" itemscope itemtype="http://schema.org/Product">
            <div class="container">
                <div class="row">
                    <div class="col-12 col-md-7 px-0 px-sm-3">
                        <div style="padding-bottom: {{ product.featured_image.dimensions['height'] / product.featured_image.dimensions['width'] * 100}}%;">
                            <img alt="{{ product.featured_image.alt }}" title="{{ product.featured_image.alt }}" src="{{ 'images/empty-placeholder.png' | static_url }}" data-src="{{ product.featured_variant_image | product_image_url('huge')}}" class="lazyload img-absolute img-absolute-centered product-slider-image" />
                            {% include 'snipplets/labels.tpl' with {'product_detail': true} %}
                        </div>
                    </div>
                    <div class="col">
                        {% include 'snipplets/product/product-form.tpl' with {quickshop: true} %}


                        {% if product.description != '' %}
                            <h5 class="mb-3">{{ "Descripción" | translate }}</h5>
                            {{ product.description | plain | truncateWords(100) }} ...
                            <a class="btn btn-link float-left mt-2" href="{{ product.url }}" title="{{ product.name }}">{{ "Ver <strong>{1}</strong> en detalle" | translate(product.name) }}</a>
                        {% endif %}
                    </div>
                </div>
            </div>
        </div>
    {% endblock %}
    {% block modal_foot %}
        <a class="btn btn-link float-right" href="#" class="js-modal-close float-right">{{ 'Cerrar' | translate }}</a>
    {% endblock %}
{% endembed %}

Este snipplet contiene dos columnas, una para la imagen y otra para el formulario de producto. Hay que mencionar que estamos llamando a dos snipplets que son propios del theme Base:

  • labels.tpl es un snipplet para mostrar los carteles de descuento, envío gratis y cuando no hay stock. En el theme Base está dentro de la carpeta snipplets.
    Si tu diseño no está basado en Base, recomendamos que crees este tpl y agregues el HTML de los carteles dentro.
  • product-form.tpl . Como su nombre indica, es un snipplet para el formulario de producto (precio, variantes, cuotas, cantidades, botón de agregar al carrito y descripción). En Base está dentro de la carpeta snipplets/product.
    Si en tu caso no tenés este tpl, reemplazá el llamado a este en product-quickshop.tpl por el HTML del formulario de producto de tu diseño, pero recordá adaptar los pasos que siguen en este tutorial en tu código (si es que aplican).

2. Agregamos el link para ver el modal o popup y el llamado al snipplet product-quickshop.tpl dentro del archivo item.tpl de la carpeta snipplets/grid, dependiendo cuan actualizado esté tu código, puede que este cambio tengas que aplicarlo en el snipplet single_product.tpl.

Para el link que abre el modal, necesitamos el siguiente código, que se ubica dentro del contexto de la imagen:

{% if settings.quick_shop %}
    <a href="#" class="js-modal-open item-quickshop btn btn-primary d-sm-block" data-toggle="#quickshop-{{ product.id }}" >
        {{ 'Vista rápida' | translate }}
    </a>
{% endif %}

Y luego debajo de todo agregar el llamado al modal, si tu diseño usa un plugin como Masonry, te recomendamos que el modal se encuentre luego de cerrar el div del item de producto.

{% if settings.quick_shop %}
    {% snipplet 'product/product-quickshop.tpl' %}
{% endif %}

3.  En el snipplet installments.tpl, que en Base está dentro de la carpeta snipplets/payments, ubicar el siguiente condicional (está dos veces):

{% if product_detail and installments_info %}

Y reemplazarlo por 

{% if product_detail and installments_info and not quickshop %}

Si tu diseño no está basado en el theme Base, el cambio anterior no aplica y es probable que necesites llamar al snipplet que muestra las cuotas en el detalle de producto, generalmente llamado installments_in_product.tpl  

4. Nuevamente, este paso solo aplica al código basado en Base theme. Ubicamos el snipplet product-form.tpl dentro de la carpeta snipplets/product

Dentro del mismo englobamos el llamado al page-header.tpl con el condicional {% if not quickshop %} quedando así:

{% if not quickshop %}
    <div itemprop="name">
        {% embed "snipplets/page-header.tpl" %}
            {% block page_header_text %}{{ product.name }}{% endblock page_header_text %}
        {% endembed %}
    </div>
{% endif %}

Dentro del product-form.tpl englobar el precio, el texto para promociones y el llamado al snipplet installments.tpl, en un div bajo el condicional  {% if quickshop %}. Queda así:

{% if quickshop %}

<div class="mb-4">

{% endif %}
…
{% if quickshop %}

</div>

{% endif %}

Ubicamos el llamado al snipplet installments.tpl que se ve así:

{% include "snipplets/payments/installments.tpl" with {'product_detail' : true} %}

Y lo reemplazamos por lo siguiente (para poder aplicar el condicional “if quickshop”)

{# Product installments #}

{% if quickshop %}
    {% include "snipplets/payments/installments.tpl" with {'product_detail' : true, 'quickshop': true}  %}
{% else %}
    {% include "snipplets/payments/installments.tpl" with {'product_detail' : true} %}
{% endif %}

Siguiendo con el formulario de producto, no vamos a mostrar el calculador de envíos ya que este popup es una vista rápida y no un formulario completo. Para evitar mostrar la parte de envíos ubicamos el condicional que lo engloba:

{% if settings.shipping_calculator_product_page and not product.free_shipping %}

Y le agregamos el condicional “not quickshop”

{% if settings.shipping_calculator_product_page and not product.free_shipping and not quickshop %}

Por último englobamos la descripción del producto con el mismo condicional, quedando así:

{% if not quickshop %}

    {# Product payments details #}

    {% include 'snipplets/payments/payments.tpl' %}

    {# Product share #}

    {% include 'snipplets/social/social-share.tpl' %}

    {# Product description #}

    {% if product.description is not empty %}
        <div class="product-description user-content">
            <h5 class="my-3">{{ "Descripción" | translate }}</h5>
            {{ product.description }}
        </div>
    {% endif %}

{% endif %}

5. Ahora tenemos que cambiar el snipplet que muestra las variantes del producto. En el caso del base se llama product-variants.tpl y está en la carpeta snipplets/product. Si tu diseño no tiene este archivo, puede que las variantes estén en el snipplet variants.tpl o directamente en el template product.tpl

Para  el código de Base, simplemente ubicamos el div con la clase js-product-variants y agregamos el siguiente condicional:

{% if quickshop %} js-product-quickshop-variants{% endif %} 

Si tu código no se parece al Base, entonces recomendamos que luego de localizar el código de las variantes, hacer lo siguiente:

Englobar el for que itera sobre las variantes con un div de la siguiente forma:

<div class="js-product-variants {% if quickshop %} js-product-quickshop-variants{% endif %} form-row">
{% for variation in product.variations %}
….
</div>

Luego en el select de cada variante agregar la clase js-variation-option y borrar el JavaScript inline que se ve similar a esto:

onchange="LS.changeVariant…”

6. Ahora necesitamos crear el snipplet para el componente modal o popup dentro de la carpeta snipplets. Este tpl se llama modal.tpl y el código es:

{# /*============================================================================
  #Modal
==============================================================================*/

#Properties
    // ID
    // Position - Top, Right, Bottom, Left
    // Transition - Slide and Fade
    // Width - Full and Box
    // modal_form_action - For modals that has a form


#Head
    // Block - modal_head
#Body
    // Block - modal_body
#Footer
    // Block - modal_footer


#}


{% set modal_overlay = modal_overlay | default(true) %}


<div id="{{ modal_id }}" class="js-modal modal modal-{{ modal_class }} modal-{{modal_position}} transition-{{modal_transition}} modal-{{modal_width}} transition-soft" style="display: none;">
    {% if modal_form_action %}
    <form action="{{ modal_form_action }}" method="post" class="{{ modal_form_class }}">
    {% endif %}
    <div class="js-modal-close modal-header">
        <span class="modal-close">
            {% include "snipplets/svg/times.tpl" with {svg_custom_class: "icon-inline svg-icon-text"} %}
        </span>
        {% block modal_head %}{% endblock %}
    </div>
    <div class="modal-body">
        {% block modal_body %}{% endblock %}
    </div>
    {% if modal_footer %}
        <div class="modal-footer d-md-block">
            {% block modal_foot %}{% endblock %}
        </div>
    {% endif %}
    {% if modal_form_action %}
    </form>
    {% endif %}
</div>

7. Por último para la parte de HTML, necesitamos agregar una carpeta SVG dentro de la carpeta snipplets. Acá vamos sumar el SVG para el icono para cerrar el popup con el nombre times.tpl

times.tpl

<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>

CSS

Requisito:

Tener agregados en tu diseño las clases helpers. Podés seguir este este pequeño tutorial para hacerlo (simplemente es copiar y pegar algunas clases, no toma más de 1 minuto).

1. Agregamos el siguiente SASS de colores en style-colors.scss.tpl (o la hoja de tu diseño que tenga los colores y tipografías de la tienda). Recordá que las variables de colores y tipografías pueden variar respecto a tu diseño:

@mixin prefix($property, $value, $prefixes: ()) {
  @each $prefix in $prefixes {
      #{'-' + $prefix + '-' + $property}: $value;
  }
    #{$property}: $value;
}


/* // Modals */


.modal{
  color: $main-foreground;
  background-color:$main-background;
}


/* // Links */


.btn-link{
  color: $primary-color;
  fill: $primary-color;
  text-transform: uppercase;
  border-bottom: 1px solid;
  font-weight: bold;
  cursor: pointer;
  &:hover,
  &:focus{
    color: rgba($primary-color, .5);
    fill: rgba($primary-color, .5);
  }
}

2. Agregar los estilos dentro del archivo static/style-critical.tpl 

Si en tu diseño usas una hoja de estilos para el CSS crítico, vamos a necesitar el siguiente código dentro de la misma, pero si no es el caso entonces podés unificar el CSS de los pasos 2 y 3 en un solo archivo.

.item-quickshop{
  position: absolute;
  bottom: -10%;
}

3. Agregar los estilos dentro del archivo static/style-async.tpl 

Si en tu diseño usas una hoja de estilos para CSS asíncrono, vamos a necesitar el siguiente código dentro de la misma, pero si no es el caso entonces podés agregarlo en la hoja de tu CSS principal.

@mixin prefix($property, $value, $prefixes: ()) {
  @each $prefix in $prefixes {
      #{'-' + $prefix + '-' + $property}: $value;
  }
    #{$property}: $value;
}


/* // Modals */


.modal {
  position: fixed;
  top: 0;
  display: block;
  width: 80%;
  height: 100%;
  padding: 10px;
  -webkit-overflow-scrolling: touch;
  overflow-y: auto;
  transition: all .2s cubic-bezier(.16,.68,.43,.99);
  z-index: 20000;
  &-header{
    width: calc(100% + 20px);
    margin: -10px 0 10px -10px;
    padding: 10px 15px;
    font-size: 20px;
  }
  &-footer{
    padding: 10px;
    clear: both;
  }
  &-full {
    width: 100%;
  }
  &-docked-sm{
    width: 100%;
  }
  &-docked-small{
    width: 80%;
  }
  &-top{
    top: -100%;
    left: 0;
  }
  &-bottom{
    top: 100%;
    left: 0;
  }
  &-left{
    left: -100%;
  }
  &-right{
    right: -100%;
  }
  &-centered{
    height: 100%;
    width: 100%;
  }
  &-top.modal-show,
  &-bottom.modal-show {
    top: 0;
  }
  &-left.modal-show {
    left: 0;
  }
  &-right.modal-show {
    right: 0;
  }
  &-close { 
    display: inline-block;
    padding: 1px 5px 5px 0;
    margin-right: 5px;
    vertical-align: middle;
    cursor: pointer;
  }
  .tab-group{
    margin:  0 -10px 20px -10px;
  }
}


.modal-overlay{
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #00000047;
  z-index: 10000;
}


{# /* // Grid item */ #}


.item-quickshop{
  left: 50%;
  transform: translateX(-50%);
  -webkit-transform: translateX(-50%);
  -ms-transform: translateX(-50%);
  z-index: 1;
  @include prefix(transition, all 0.2s ease, webkit ms moz o);
}


.item-image:hover .item-quickshop{
  bottom: 50%;
}

/* // Min width 768px */


@media (min-width: 768px) { 


/* //// Components */


 /* Modals */


  .modal{
    &-centered{
      height: 80%;
      width: 80%;
      left: 10%;
      margin: 5% auto;
    }
    &-docked-sm{
      width: 500px;
    }
    &-docked-small{
      width: 350px;
    }
  }
}

JS

1. El JavaScript necesitamos agregarlo en el archivo store.js.tpl (o donde tengas tus funciones de JS).  Agregamos el siguiente código :

El JS para que funcionen los modals

{#/*============================================================================
      #Modals
    ==============================================================================*/ #}


    var $modal_close = $('.js-modal-close');
    var $modal_open = $('.js-modal-open');
    
    $modal_open.click(function (e) {
        e.preventDefault(); 
        var $modal_id = $(this).data('toggle');
        $(".js-modal-overlay").fadeToggle();
        if ($($modal_id).hasClass("modal-show")) {
            $($modal_id).removeClass("modal-show").delay(200).hide(0);
        } else {
            $($modal_id).detach().insertAfter(".js-modal-overlay").show(0).addClass("modal-show");
        }             
    });


    $modal_close.click(function (e) {
        e.preventDefault();  
        $(this).closest(".js-modal").removeClass("modal-show").delay(200).hide(0); 
        $(".js-modal-overlay").fadeOut(300);     
    });


    $(".js-modal-overlay").click(function (e) {
        e.preventDefault();  
        $(".js-modal.modal-show").removeClass("modal-show").delay(200).hide(0);   
        $(this).fadeOut(300);   
    });

El JS para actualizar el producto al cambiar de variante:

$(document).on("change", ".js-variation-option", function(e) {
    var $parent = $(this).closest(".js-product-variants");
    var quick_id = $(this).closest(".js-quickshop-container").attr("id");
    if($parent.hasClass("js-product-quickshop-variants")){
        LS.changeVariant(changeVariant, '#' + quick_id);
    } else {
        LS.changeVariant(changeVariant, '#single-product');
    }
});

2. Ubicamos la función LS.addToCartEnhanced, que se usa al agregar un producto al carrito ajax, y dentro de la siguiente función:

Dentro del callback callback_add_to_cart, agregamos lo siguiente:

// Hide modal if it is quickshop
$(".js-modal.modal-show").removeClass("modal-show").delay(200).hide(0); 
$(".js-modal-overlay").fadeOut(300);   

Si no tenés el callback crealo con este código:

var callback_add_to_cart = function(){


}

3. Si tu diseño no se encuentra basado en Base, no vas necesitar este paso. De lo contrario, en el archivo external.js.tpl dentro de la carpeta static/js agregar el JS de los modals de Bootstrap:

/*!
 * Bootstrap v3.4.1 (https://getbootstrap.com/)
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under the MIT license
 */

if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(t){"use strict";var e=t.fn.jquery.split(" ")[0].split(".");if(e[0]<2&&e[1]<9||1==e[0]&&9==e[1]&&e[2]<1||e[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(t){"use strict";function e(e,o){return this.each(function(){var s=t(this),n=s.data("bs.modal"),a=t.extend({},i.DEFAULTS,s.data(),"object"==typeof e&&e);n||s.data("bs.modal",n=new i(this,a)),"string"==typeof e?n[e](o):a.show&&n.show(o)})}var i=function(e,i){this.options=i,this.$body=t(document.body),this.$element=t(e),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.fixedContent=".navbar-fixed-top, .navbar-fixed-bottom",this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,t.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};i.VERSION="3.4.1",i.TRANSITION_DURATION=300,i.BACKDROP_TRANSITION_DURATION=150,i.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},i.prototype.toggle=function(t){return this.isShown?this.hide():this.show(t)},i.prototype.show=function(e){var o=this,s=t.Event("show.bs.modal",{relatedTarget:e});this.$element.trigger(s),this.isShown||s.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',t.proxy(this.hide,this)),this.$dialog.on("mousedown.dismiss.bs.modal",function(){o.$element.one("mouseup.dismiss.bs.modal",function(e){t(e.target).is(o.$element)&&(o.ignoreBackdropClick=!0)})}),this.backdrop(function(){var s=t.support.transition&&o.$element.hasClass("fade");o.$element.parent().length||o.$element.appendTo(o.$body),o.$element.show().scrollTop(0),o.adjustDialog(),s&&o.$element[0].offsetWidth,o.$element.addClass("in"),o.enforceFocus();var n=t.Event("shown.bs.modal",{relatedTarget:e});s?o.$dialog.one("bsTransitionEnd",function(){o.$element.trigger("focus").trigger(n)}).emulateTransitionEnd(i.TRANSITION_DURATION):o.$element.trigger("focus").trigger(n)}))},i.prototype.hide=function(e){e&&e.preventDefault(),e=t.Event("hide.bs.modal"),this.$element.trigger(e),this.isShown&&!e.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),t(document).off("focusin.bs.modal"),this.$element.removeClass("in").off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.modal"),t.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",t.proxy(this.hideModal,this)).emulateTransitionEnd(i.TRANSITION_DURATION):this.hideModal())},i.prototype.enforceFocus=function(){t(document).off("focusin.bs.modal").on("focusin.bs.modal",t.proxy(function(t){document===t.target||this.$element[0]===t.target||this.$element.has(t.target).length||this.$element.trigger("focus")},this))},i.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",t.proxy(function(t){27==t.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},i.prototype.resize=function(){this.isShown?t(window).on("resize.bs.modal",t.proxy(this.handleUpdate,this)):t(window).off("resize.bs.modal")},i.prototype.hideModal=function(){var t=this;this.$element.hide(),this.backdrop(function(){t.$body.removeClass("modal-open"),t.resetAdjustments(),t.resetScrollbar(),t.$element.trigger("hidden.bs.modal")})},i.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},i.prototype.backdrop=function(e){var o=this,s=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var n=t.support.transition&&s;if(this.$backdrop=t(document.createElement("div")).addClass("modal-backdrop "+s).appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",t.proxy(function(t){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(t.target===t.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),n&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!e)return;n?this.$backdrop.one("bsTransitionEnd",e).emulateTransitionEnd(i.BACKDROP_TRANSITION_DURATION):e()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var a=function(){o.removeBackdrop(),e&&e()};t.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",a).emulateTransitionEnd(i.BACKDROP_TRANSITION_DURATION):a()}else e&&e()},i.prototype.handleUpdate=function(){this.adjustDialog()},i.prototype.adjustDialog=function(){var t=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&t?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!t?this.scrollbarWidth:""})},i.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},i.prototype.checkScrollbar=function(){var t=window.innerWidth;if(!t){var e=document.documentElement.getBoundingClientRect();t=e.right-Math.abs(e.left)}this.bodyIsOverflowing=document.body.clientWidth<t,this.scrollbarWidth=this.measureScrollbar()},i.prototype.setScrollbar=function(){var e=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"";var i=this.scrollbarWidth;this.bodyIsOverflowing&&(this.$body.css("padding-right",e+i),t(this.fixedContent).each(function(e,o){var s=o.style.paddingRight,n=t(o).css("padding-right");t(o).data("padding-right",s).css("padding-right",parseFloat(n)+i+"px")}))},i.prototype.resetScrollbar=function(){this.$body.css("padding-right",this.originalBodyPad),t(this.fixedContent).each(function(e,i){var o=t(i).data("padding-right");t(i).removeData("padding-right"),i.style.paddingRight=o?o:""})},i.prototype.measureScrollbar=function(){var t=document.createElement("div");t.className="modal-scrollbar-measure",this.$body.append(t);var e=t.offsetWidth-t.clientWidth;return this.$body[0].removeChild(t),e};var o=t.fn.modal;t.fn.modal=e,t.fn.modal.Constructor=i,t.fn.modal.noConflict=function(){return t.fn.modal=o,this},t(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(i){var o=t(this),s=o.attr("href"),n=o.attr("data-target")||s&&s.replace(/.*(?=#[^\s]+$)/,""),a=t(document).find(n),r=a.data("bs.modal")?"toggle":t.extend({remote:!/#/.test(s)&&s},a.data(),o.data());o.is("a")&&i.preventDefault(),a.one("show.bs.modal",function(t){t.isDefaultPrevented()||a.one("hidden.bs.modal",function(){o.is(":visible")&&o.trigger("focus")})}),e.call(a,r,this)})}(jQuery);

Configuraciones

En el archivo config/settings.txt vamos a agregar un checkbox para activar y desactivar la funcionalidad. Vamos a ubicarlo dentro de la sección Listado de productos.

    title
        title = Pop-up de compra rápida
    checkbox
        name = quick_shop
        description = Permitir que tus clientes puedan agregar productos a su carrito rápidamente desde una ventana emergente en el listado de productos

Traducciones

En este paso agregamos los textos para las traducciones en el archivo config/translations.txt

es "Vista rápida"
pt "Espiar"
en "Quickshop"
es_mx "Vista rápida"

es "Ver <strong>{1}</strong> en detalle"
pt "Ver <strong>{1}</strong> em detalhes"
en "See <strong>{1}</strong> in detail"
es_mx "Ver <strong>{1}</strong> en detalle"

es "Pop-up de compra rápida"
pt "Visualização rápida"
es_mx "Pop-up de compra rápida"

es "Permitir que tus clientes puedan agregar productos a su carrito rápidamente desde el listado de productos"
pt "Permitir que seus clientes possam agregar produtos ao seu carrinho rapidamente na lista de produtos"
es_mx "Permitir que tus clientes puedan agregar productos a su carrito rápidamente desde el listado de productos"

Activación

Por último podés activar el modal desde el Administrador nube, en la sección de Personalizar tu diseño actual dentro de las Listado de productos: