Producto disponible para compra con suscripción

En el este artículo vamos a agregar la modalidad de compra por suscripción para los productos que lo permitan. 











Algunas consideraciones antes de avanzar sobre esta modalidad de compra:

  • Sólo se paga en 1 pago a través de Pago Nube
  • Si un producto tiene "Compra rápida" y es apto para suscripción, la funcionalidad de "Compra rápida" será anulada, haciendo que el usuario vaya directamente a la página de detalle del producto.
  • Al avanzar con la modalidad de suscripción, el usuario avanzará directamente al checkout con ese producto, perdiendo los productos que ya tenga en el carrito.

Antes de comenzar es importante que tengas los siguientes componentes privados implementados en tu tienda:

  • Medios de pago
  • Etiquetas de envío gratis, promociones y stock
  • Promociones en el detalle del producto

HTML

1. Dentro de tu repositorio busquemos el siguiente div:

<div class="subtotal-price hidden" data-priceraw="{{ cart.total }}"></div>

Y sumemos la clase "js-subtotal-price".

2. En la carpeta snipplets busquemos el archivo relacionado al item de producto en los listados, en este caso el item.tpl, y buscamos la siguiente condición que permite incluir el formulario para la compra rápida:

{% if (settings.quick_shop or settings.product_color_variants) and product.available and product.display_price and product.variations and not reduced_item %}
     <div class="js-item-variants hidden">
     .....

Y reemplazamos la condición por algo como lo siguiente:

{% if 
    ((settings.quick_shop and not product.isSubscribable()) or settings.product_color_variants)
    and product.available 
    and product.display_price 
    and product.variations and not reduced_item 
%}

Este cambio garantiza que si un producto es apto para suscripción, el usuario será enviado a la página de detalle del producto donde podrá elegir esta modalidad de compra.

En este mismo archivo vamos a incluir el componente del mensaje que muestra si un producto es apto para suscripción y si tiene descuento con esta modalidad (recomendamos incluirlo cerca del contexto del precio del producto)

{{ component('subscriptions/subscription-message', {
    subscription_classes: {
        container: 'text-accent mt-2',
    },
}) }}

Debería verse similar a esto:


Los parámetros que este componente acepta son los siguientes:

Parámetros 

ParámetroDescripción
svg_spritesBooleano que determina si se usan SVG sprites
subscription_iconBooleano para permitir usar un ícono en el mensaje
subscription_icon_svg_idUsado para el ID del SVG sprite del ícono
subscription_custom_iconUsado en caso de no implementar SVG sprites, para aplicar HTML personalizado en el ícono.

CSS

ParámetroDescripción
containerUsado para el contenedor del mensaje
iconUsado para el ícono del mensaje
textUsado para contenedor del texto cuando se usa un ícono
valueUsado para el valor del descuento en caso de ofrecer uno

label

Usado para el texto del mensaje (sin incluir el descuento)

Por último para este archivo necesitamos agregar un "falso" botón de "Compra rápida" que en lugar de abrir la misma, llevará al usuario a la página de producto. Para hacer este cambio hay que encontrar el siguiente código que contiene los botones de "Compra rápida"

{% if product.variations %}
    {# Open quickshop popup if has variants #}
    <a data-toggle="#quickshop-modal" data-modal-url="modal-fullscreen-quickshop" class="js-quickshop-modal-open {% if slide_item %}js-quickshop-slide{% endif %} js-modal-open js-fullscreen-modal-open btn btn-primary btn-small px-4" title="{{ 'Compra rápida de' | translate }} {{ product.name }}" aria-label="{{ 'Compra rápida de' | translate }} {{ product.name }}" data-component="product-list-item.add-to-cart" data-component-value="{{product.id}}">{{ 'Agregar al carrito' | translate }}</a>
{% else %}
    {# If not variants add directly to cart #}
    <form class="js-product-form" method="post" action="{{ store.cart_url }}">
        <input type="hidden" name="add_to_cart" value="{{product.id}}" />
        {% set state = store.is_catalog ? 'catalog' : (product.available ? product.display_price ? 'cart' : 'contact' : 'nostock') %}
        {% set texts = {'cart': "Agregar al carrito", 'contact': "Consultar precio", 'nostock': "Sin stock", 'catalog': "Consultar"} %}
        <input type="number" name="quantity" value="1" class="js-quantity-input hidden" aria-label="{{ 'Cambiar cantidad' | translate }}" >
        <input type="submit" class="js-addtocart js-prod-submit-form btn btn-primary btn-small {{ state }} px-4 mb-1 mx-auto" value="{{ texts[state] | translate }}" {% if state == 'nostock' %}disabled{% endif %} data-component="product-list-item.add-to-cart" data-component-value="{{ product.id }}"/>
        {# Fake add to cart CTA visible during add to cart event #}
        {% include 'snipplets/placeholders/button-placeholder.tpl' with {custom_class: "js-addtocart-placeholder-inline btn-small mb-1 mx-auto"} %}
    </form>
{% endif %}

Y reemplazarlo por lo siguiente:

{# Trigger quickshop actions #}

{% set quickshop_button_classes = 'btn btn-primary btn-small px-4 mb-1 mx-auto' %}
{% set state = store.is_catalog ? 'catalog' : (product.available ? product.display_price ? 'cart' : 'contact' : 'nostock') %}
{% set texts = {'cart': "Agregar al carrito", 'contact': "Consultar precio", 'nostock': "Sin stock", 'catalog': "Consultar"} %}

<div class="item-actions mt-2">
    {% if product.isSubscribable() %}
        {# Product with subscription will link to the product page #}
        <a href="{{ product_url_with_selected_variant }}" class="{{ quickshop_button_classes }}" title="{{ 'Compra rápida de' | translate }} {{ product.name }}" aria-label="{{ 'Compra rápida de' | translate }} {{ product.name }}">{{ texts[state] | translate }}</a>
    {% else %}
        {% if product.variations %}
            {# Open quickshop popup if has variants #}
            <a data-toggle="#quickshop-modal" data-modal-url="modal-fullscreen-quickshop" class="js-quickshop-modal-open {% if slide_item %}js-quickshop-slide{% endif %} js-modal-open js-fullscreen-modal-open {{ quickshop_button_classes }}" title="{{ 'Compra rápida de' | translate }} {{ product.name }}" aria-label="{{ 'Compra rápida de' | translate }} {{ product.name }}" data-component="product-list-item.add-to-cart" data-component-value="{{product.id}}">{{ 'Agregar al carrito' | translate }}</a>
        {% else %}
            {# If not variants add directly to cart #}
            <form class="js-product-form" method="post" action="{{ store.cart_url }}">
                <input type="hidden" name="add_to_cart" value="{{product.id}}" />
                
                <input type="number" name="quantity" value="1" class="js-quantity-input hidden" aria-label="{{ 'Cambiar cantidad' | translate }}" >
                <input type="submit" class="js-addtocart js-prod-submit-form {{ quickshop_button_classes }} {{ state }}" value="{{ texts[state] | translate }}" {% if state == 'nostock' %}disabled{% endif %} data-component="product-list-item.add-to-cart" data-component-value="{{ product.id }}"/>
                {# Fake add to cart CTA visible during add to cart event #}
                {% include 'snipplets/placeholders/button-placeholder.tpl' with {custom_class: "js-addtocart-placeholder-inline btn-small mb-1 mx-auto"} %}
            </form>
        {% endif %}
    {% endif %}
</div>

La parte más relevante es donde abre el condicional {% if product.isSubscribable() %} que incluye dentro el nuevo botón que lleva a la página de producto sólo si este es apto para suscripción.

3. Luego tenemos que sumar los cambios en el formulario de producto dentro del archivo product-form.tpl que se usa en la página del detalle de producto.

Primero sumaremos la clase "js-price-container" al div que contiene a los precios del producto (precio de base y precio promocional).

Por fuera de ese div y debajo vamos a incluir el componente de precio por suscripción

{{ component('subscriptions/subscription-price', {
    subscription_classes: {
        container: 'text-center text-md-left mb-3',
        prices_container: 'mb-1',
        price_compare: 'h4 price-compare mb-0',
        price_with_subscription: 'h4 mb-0',
        discount_container: 'h6 text-accent mb-1',
        price_without_taxes_container: 'mb-2 font-small opacity-60',
    },
}) }}

Debería verse similar a esto:

Los parámetros que este componente acepta son los siguientes:

Parámetros 

ParámetroDescripción
subscription_discount_positionUsado para ubicar el mensaje de descuento arriba de los precios (usando "above"), debajo (usando "below") o al costado (usando "inline"). Del default es debajo.

CSS

ParámetroDescripción
containerUsado para el contenedor general
discountUsado para el contenedor del mensaje de descuento
discount_valueUsado para el porcentaje de descuento
labelUsado para el texto de descuento

discount_container

Usado para el contenedor principal del mensaje de descuento
prices_containerUsado para el contenedor de los precios (precio de base y precio con descuento)
price_compareUsado para el precio tachado cuando existe un descuento
price_with_subscriptionUsado para el precio final por suscripción

Ahora necesitamos buscar el elemento que muestra el mensaje de envío gratis a partir de un mínimo. Este generalmente tiene la clase "free-shipping-message" y se encuentra dentro del siguiente condicional:

{% if not product.is_non_shippable and show_product_quantity and (has_free_shipping or has_product_free_shipping) %}

Y sumamos la clase "js-free-shipping-minimum-message", dejándolo así:

{% if not product.is_non_shippable and show_product_quantity and (has_free_shipping or has_product_free_shipping) %}
    <div class="js-free-shipping-minimum-message free-shipping-message text-center text-md-left mb-4 pb-2">
        <span class="d-inline-block">
            {% include "snipplets/svg/truck.tpl" with {svg_custom_class: "icon-inline icon-w-18 icon-lg svg-icon-accent mr-2"} %}
        </span>
        <span class="d-inline-block">
            <strong class="text-accent">{{ "Envío gratis" | translate }} </strong>
            <span {% if has_product_free_shipping %}style="display: none;"{% else %}class="js-shipping-minimum-label"{% endif %}>
                {{ "superando los" | translate }} <span>{{ cart.free_shipping.min_price_free_shipping.min_price }}</span>
            </span>
        </span>
        {% if not has_product_free_shipping %}
            <div class="js-free-shipping-discount-not-combinable font-small mt-1">
                {{ "No acumulable con otras promociones" | translate }}
            </div>
        {% endif %}
    </div>
{% endif %}

De la misma manera buscamos el elemento con el mensaje "¡Genial! Tenés envío gratis"  y sumamos la clase "js-product-form-free-shipping-message" quedando de la siguiente forma:

<div class="js-product-form-free-shipping-message {% if free_shipping_minimum_label_changes_visibility %}js-free-shipping-message{% endif %} text-accent font-weight-bold mb-4 text-center text-md-left h6" {% if not cart.free_shipping.cart_has_free_shipping %}style="display: none;"{% endif %}>
    {{ "¡Genial! Tenés envío gratis" | translate }}
</div>

Por último justo arriba del botón de compra colocamos el componente que permite elegir entre 2 modalidades de compra: Compra única y compra por suscripción:

{{ component('subscriptions/subscription-selector', {
    subscription_classes: {
        container: 'radio-button-container box p-0 mb-3',
        radio_button: 'radio-button-item',
        radio_button_text: 'row',
        radio_button_icon: 'radio-button-icons',
        purchase_option_info_container: 'col pr-0',
        purchase_option_price: 'col-auto text-right font-weight-bold',
        purchase_option_single_frequency: 'mt-2 pt-1 font-small opacity-80',
        purchase_option_discount: 'label label-accent font-smallest px-2 py-1 ml-1',
        dropdown_container: 'form-group font-small mt-2 mb-0',
        dropdown_button: 'form-select position-relative',
        dropdown_icon: 'form-select-icon icon-inline icon-w-14',
        dropdown_options: 'form-select-options',
        dropdown_option: 'form-select-option row no-gutters',
        dropdown_option_info: 'col pr-4',
        dropdown_option_price: 'col-auto font-weight-bold',
        dropdown_option_discount: 'text-accent mt-1 font-weight-bold',
        cart_alert: 'text-center subscription-btn-alert full-width-container mb-4 pb-2',
        shipping_message: 'mt-2 mb-4',
        shipping_message_title: 'font-weight-bold ml-1',
        shipping_message_text: 'font-small mt-2 ml-4',
        legal_message: 'font-smallest text-center mb-3',
        legal_link: 'font-smallest d-inline-block btn-link btn-link-primary p-0',
        legal_modal: 'bottom modal-centered-small modal-centered transition-soft',
        legal_modal_header: 'modal-header row no-gutters align-items-center',
        legal_modal_title: 'col',
        legal_modal_close_button: 'col-auto mr-3 pb-0 order-first',
        legal_modal_body: 'mb-4',
        legal_modal_details_title: 'h6 mb-2',
        legal_modal_details_paragraph: 'font-small pb-4 mb-0',
        legal_modal_details_link: 'font-small d-inline-block btn-link btn-link-primary p-0'
    },
    svg_sprites: false,
    dropdown_icon: true,
    dropdown_custom_icon: include("snipplets/svg/chevron-down.tpl", { svg_custom_class: "icon-inline icon-sm svg-icon-text" }),
    shipping_message_icon: true,
    shipping_message_custom_icon: include("snipplets/svg/truck.tpl", { svg_custom_class: "icon-inline icon-lg icon-w-18 svg-icon-text" }),
    legal_modal_close_custom_icon: include("snipplets/svg/times.tpl", { svg_custom_class: "icon-inline svg-icon-text" }),
}) }}

Debería verse similar a esto:

Los parámetros que este componente acepta son los siguientes:

Parámetros 

ParámetroDescripción
svg_spritesBooleano que determina si se usan SVG sprites
dropdown_iconBooleano para permitir usar un ícono para el desplegable que permite elegir las opciones dentro de la modalidad suscripción
dropdown_icon_svg_idUsado para el ID del SVG sprite del desplegable
dropdown_custom_iconUsado en caso de no implementar SVG sprites, para aplicar HTML personalizado en el ícono del desplegable
cart_alert_iconBooleano usado para el ícono en el mensaje que avisa que se perderán los productos del carrito en caso de avanzar con la suscripción
cart_alert_icon_svg_idUsado para el ID del SVG en el mensaje sobre los productos en el carrito
cart_alert_custom_iconUsado en caso de no implementar SVG sprites, para aplicar HTML personalizado en el ícono del mensaje sobre los productos en el carrito 
shipping_message_iconBooleano usado para el ícono en el mensaje que avisa que las opciones de envío se elegirán en el checkout
shipping_message_icon_svg_idUsado para el ID del SVG sprite del mensaje sobre la opciones de envío
shipping_message_custom_iconUsado en caso de no implementar SVG sprites, para aplicar HTML personalizado en el ícono del mensaje sobre las opciones de envío

CSS

ParámetroDescripción
containerUsado para el contenedor general
purchase_option_info_containerUsado para el contenedor la información en el radio button de cada modalidad de compra
purchase_option_nameUsado para el nombre de la modalidad de compra en cada radio button
purchase_option_discountUsado para el descuento en el radio button con la modalidad de compra por suscripción

purchase_option_price

Usado para el precio de la modalidad de compra en cada radio button
purchase_option_single_frequencyUsado para el texto que muestra el tiempo en el radio button con la modalidad de compra por suscripción cuando sólo tiene 1 frecuencia disponible.
purchase_option_price_subscriptionUsado para el precio tachado cuando existe un descuento
dropdown_option_infoUsado para la información (frecuencia y descuento) en cada opción de suscripción dentro del desplegable
dropdown_option_frequencyUsado para la información del tiempo en cada opción de suscripción dentro del desplegable
dropdown_option_discountUsado para el descuento en cada opción de suscripción dentro del desplegable
dropdown_option_priceUsado para el precio en cada opción de suscripción dentro del desplegable
cart_alertUsado para el mensaje que comunica que los productos en el carrito se eliminaran al avanzar con la compra por suscripción
cart_alert_iconUsado para el ícono del mensaje que comunica que los productos en el carrito se eliminaran al avanzar con la compra por suscripción
cart_alert_textUsado para el texto del mensaje que comunica que los productos en el carrito se eliminaran al avanzar con la compra por suscripción
shipping_messageUsado para el mensaje que comunica que las opciones de envío se elegirán en el checkout
shipping_message_title_containerUsado para el contenedor del título en el mensaje que comunica que las opciones de envío se elegirán en el checkout, en caso de usar ícono
shipping_message_iconUsado para el ícono en el mensaje que comunica que las opciones de envío se elegirán en el checkout
shipping_message_titleUsado para el título en el mensaje que comunica que las opciones de envío se elegirán en el checkout
shipping_message_textUsado para el texto en el mensaje que comunica que las opciones de envío se elegirán en el checkout
legal_messageUsado para el mensaje de términos y condiciones sobre la compra por suscripción
legal_linkUsado para los links en el mensaje de términos y condiciones sobre la compra por suscripción
legal_modalUsado para los modales de términos y condiciones sobre la compra por suscripción
legal_modal_headerUsado para el encabezado en los modales de términos y condiciones sobre la compra por suscripción
legal_modal_titleUsado para el título en los modales de términos y condiciones sobre la compra por suscripción
legal_modal_close_iconUsado para el ícono de cierre en los modales de términos y condiciones sobre la compra por suscripción
legal_modal_close_buttonUsado para el botón de cierre en los modales de términos y condiciones sobre la compra por suscripción
legal_modal_bodyUsado para el cuerpo en los modales de términos y condiciones sobre la compra por suscripción
legal_modal_overlayUsado para el overlay en los modales de términos y condiciones sobre la compra por suscripción
legal_modal_details_titleUsado en los subtítulos para el contenido de los los modales de términos y condiciones sobre la compra por suscripción
legal_modal_details_paragraphUsado en los párrafos para el contenido de los los modales de términos y condiciones sobre la compra por suscripción
legal_modal_details_linkUsado en los links dentro del contenido de los los modales de términos y condiciones sobre la compra por suscripción

Último paso opcional, si tu diseño usa el archivo button-placeholder.tpl y la transición al agregar un producto al carrito muestra un texto "Agregando...", necesitamos sumar la clase "js-addtocart-adding-text" al div que tiene la clase "js-addtocart-adding", quedando de la siguiente manera:

<div class="js-addtocart js-addtocart-placeholder btn btn-primary btn-transition disabled {{ custom_class }}" style="display: none;">
    <div class="d-inline-block">
        <span class="js-addtocart-text transition-container btn-transition-start active">
            {{ 'Agregar al carrito' | translate }}
        </span>
        <span class="js-addtocart-success transition-container btn-transition-success">
            {{ '¡Listo!' | translate }}
        </span>
        <div class="js-addtocart-adding js-addtocart-adding-text transition-container btn-transition-progress">
            {{ 'Agregando...' | translate }}
        </div>
    </div>
</div>

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.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.

.form-group .form-select-icon,
.form-select .form-select-icon{
  position: absolute;
  bottom: 12px;
  right: 0;
  pointer-events: none;
}
.form-select .form-select-icon {
  top: 50%;
  bottom: initial;
  transform: translateY(-50%);
  -webkit-transform: translateY(-50%);
  -ms-transform: translateY(-50%);
}

.hidden-important{
    display:none!important;
}

Opcional: Dentro de este mismo archivo, vamos a sumar los estilos relacionados al componente de radio button. En caso de que los tengas en otro archivo de CSS, deberíamos traerlos al crítico ya que ahora el componente de radio button pasa a ser más relevante.

.radio-button {
  margin-bottom: 0;
  -webkit-tap-highlight-color: rgba(0,0,0,0);
  cursor: pointer;
}
.radio-button.disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
.radio-button.disabled input[type="radio"] {
  cursor: not-allowed;
}
.radio-button-content {
  position: relative;
  width: 100%;
  float: left;
  padding: 15px; 
  clear: both;
  box-sizing: border-box;
}
.radio-button-icons-container {
  position: absolute;
  top: 14px;
  left: 10px;
}
.radio-button-icons {
  position: relative;
  float: left;
}
.radio-button-icon {
  width: 16px;
  height: 16px;
  border-radius: 50%;
}
.radio-button input[type="radio"] {
  display: none;
}
.radio-button input[type="radio"] + .radio-button-content .unchecked {
  float: left;
}
.radio-button input[type="radio"] + .radio-button-content .checked {
  position: absolute;
  top: 8px;
  left: 8px;
  width: 0;
  height: 0;      
  -webkit-transform: translate(-50%,-50%);
  -ms-transform: translate(-50%,-50%);
  -moz-transform: translate(-50%,-50%);
  -o-transform: translate(-50%,-50%);
  transform: translate(-50%,-50%);
  -webkit-transition: all 0.2s;
  -ms-transition: all 0.2s;
  -moz-transition: all 0.2s;
  -o-transition: all 0.2s;
  transition: all 0.2s;
}
.radio-button input[type="radio"]:checked + .radio-button-content .checked {
  width: 8px;
  height: 8px;
}
.radio-button-label {
  width: 100%;
  float: left;
  padding-left: 30px;
}
.radio-button-item:last-of-type .radio-button {
  margin-bottom: 0;
}

2. Agregamos 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.

{# /* // Dropdown */ #}

.form-select {
  display: block;
  width: 100%;
  &:focus{
    outline:0;
  }
  &::-ms-expand {
    display: none;
  }
  .form-select-icon {
    @include prefix(transition, all 0.2s ease, webkit ms moz o);
  }

  &.open .form-select-icon {
    @include prefix(transform, translateY(-50%) rotate(180deg), webkit ms moz o);
  }
}

.form-select-options {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 200;
  width: 100%;
  max-height: 200px;
  margin-top: 5px;
  list-style: none;
  overflow-y: auto;
  @include prefix(transition, all 0.2s ease, webkit ms moz o);
  opacity: 0;
  &.open {
    opacity: 1;
  }
}


.form-select-option {
  padding: 12px;
  font-size: var(--font-small);
  @include prefix(transition, all 0.4s ease, webkit ms moz o);
  cursor: pointer;
}

{# /* // Buttons */ #}

.btn-transition {
  position: relative;
  overflow: hidden;
  .transition-container {
    position: absolute;
    top: 50%;
    left: 0;
    width: 100%;
    margin-top: -7px;
    opacity: 0;
    text-align: center;
    @include prefix(transition, all 0.5s ease, webkit ms moz o);
    cursor: not-allowed;
    pointer-events: none;
    &.active {
      opacity: 1;
    }
  }
}

{# /* // 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;
  &-img-full{
    max-width: 100%;
    max-height: 190px;
  }
  &-header{
    width: calc(100% + 20px);
    margin: -10px 0 10px -20px;
    padding: 10px 15px 10px 25px;
    font-size: 20px;
  }
  &-footer{
    padding: 10px 0;
    clear: both;
  }
  &-with-fixed-footer {
    display: flex;
    flex-direction: column;
    height: 100%;
    .modal-scrollable-area {
      height: 100%;
      overflow: auto;
    }
  }
  &-full {
    width: 100%;
  }
  &-docked-md{
    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%;
    &-small{
      left: 50%;
      width: 80%;
      height: auto;
      @include prefix(transform, translate(-50%, 0), webkit ms moz o);
      .modal-body{
        min-height: 150px;
        max-height: 400px;
        overflow: auto;
      }
    }
    &-md.modal-show {
        left: 50%;
        transform: translateX(-50%);
        &.modal-bottom-md,
        &.modal-bottom {
          top: 50%;
          bottom: auto;
          left: 50%;
          height: fit-content;
          transform: translate(-50%, -50%);
        }
      }
  }
  &-top.modal-show,
  &-bottom.modal-show {
    top: 0;
    &.modal-centered-small{
      top: 50%;
      @include prefix(transform, translate(-50%, -50%), webkit ms moz o);
    }
  }
  &-bottom-sheet {
    top: initial;
    bottom: -100%;
    height: auto;
    &.modal-show {
      top: initial;
      bottom: 0;
      height: auto;
    }
  }
  &-left.modal-show {
    left: 0;
  }
  &-right.modal-show {
    right: 0;
  }
  &-close { 
    display: inline-block;
    padding: 1px 5px 5px 0;
    margin-right: 5px;
    font-size: 20px;
    vertical-align: middle;
    cursor: pointer;
    border: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    background: none;
  }
  .tab-group{
    margin:  0 -10px 20px -10px;
  }
}

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

@media (min-width: 768px) { 
 
 {# /* Modals */ #}
 
 .modal{
  &-centered{
    height: 80%;
    width: 80%;
    left: 10%;
    margin: 5% auto;
    &-small{
      left: 50%;
      width: 30%;
      height: auto;
      max-height: 80%;
      margin: 0;
    }
    &-md-600px {
      left: 50%;
      width: 600px;
      transform: translateX(-50%);
    }
  }
  &-centered-md.modal-show {
    left: initial;
    transform: none;
    &.modal-bottom {
      top: 50%;
    }
  }
  &-docked-md{
    width: 500px;
    overflow-x: hidden;
    &-centered{
      left: calc(50% - 250px);
      bottom: auto;
      height: auto;
    }
  }
  &-bottom-sheet {
    top: 100%;
    &.modal-show {
      top: 0;
      bottom: auto;
    }
  }
  &-docked-small{
    width: 350px;
  }
  &-md-width-400px {
    width: 400px;
    max-width: 90vw;
  }
}

3. Por último para el CSS, vamos a sumar los siguientes estilos en el archivo style-colors.scss.tpl o donde tengas los estilos relacionados a los colores de la tienda:

.form-select-options {
  background-color: $main-background;
  border: 1px solid rgba($main-foreground, .1);
}

.form-select-options::-webkit-scrollbar {
  width: 7px;
}

.form-select-options::-webkit-scrollbar-track {
  background: rgba($main-background, .5);
  border-radius: 6px;
}

.form-select-options::-webkit-scrollbar-thumb {
  background: rgba($main-foreground, .5);
  border-radius: 6px;
}

.form-select-option:hover,
.form-select-option:active {
  background-color: rgba($main-foreground, .05);
}

.form-select-option.selected {
  background-color: rgba($main-foreground, .08);
}

JS

1. El JavaScript necesitamos agregarlo en el archivo store.js.tpl dentro de la función changeVariant, al final de la misma:

LS.subscriptionChangeVariant(variant);

2. Buscamos el momento en el que se agrega un producto al carrito:

jQueryNuvem(document).on("click", ".js-addtocart:not(.js-addtocart-placeholder)", function (e) {

Y justo debajo de la siguiente parte:

 if (!jQueryNuvem(this).hasClass('contact')) {

Sumamos este Javascript:

{# Hide real button and show button placeholder during event #}

$productButton.hide();
$productButtonPlaceholder.show().addClass("active");
$productButtonText.removeClass("active");
setTimeout(function(){
    $productButtonAdding.addClass("active");
},300);

{# Restore button state in case of error #}

function restore_button_initial_state(){
    $productButtonPlaceholder.removeClass("active");
    $productButtonText.fadeIn();
    $productButtonAdding.removeClass("active");
    $productButtonPlaceholder.hide();
    $productButton.css('display' , 'inline-block');
}

{# Restore button state for subscriptions stock error #}

var subscription_callback_error = function() {
    setTimeout(function() {
        restore_button_initial_state();
    }, 500);
}

{# Handle subscribable product submit #}

const subscriptionValidResult = LS.subscriptionSubmit($productContainer, subscription_callback_error, e);
if (subscriptionValidResult && subscriptionValidResult.changeCartSubmit) {
    return;
}

Esto hará que al comprar un producto por suscripción, se avance directamente al checkout con ese producto.

Activación

De momento el equipo de Tiendanube se encargará de la activación, así que no dudes en contactarnos ni bien termines de aplicar estos cambios a storefronts@tiendanube.com

Listo, ya tenés en tu diseño la funcionalidad aplicada ¡Excelente!