Produto disponível para compra com assinatura

Neste artigo vamos adicionar a modalidade de compra por assinatura para os produtos que a permitirem.












Algumas considerações antes de avançar sobre esta modalidade de compra:

  • O pagamento é realizado em uma única vez através do Nuvem Pago.

  • Se um produto tiver a opção de "Compra rápida" e for elegível para assinatura, a funcionalidade de "Compra rápida" será desativada, direcionando o usuário diretamente para a página de detalhes do produto.

  • Ao avançar com a modalidade de assinatura, o usuário será levado diretamente ao checkout com esse produto, perdendo os itens que já estiverem no carrinho.

Antes de começar, é importante que você tenha os seguintes componentes privados implementados na sua loja:

  • Meios de pagamento

  • Selos de frete grátis, promoções e estoque

  • Promoções na página de detalhes do produto

HTML

1. Dentro do seu repositório, vamos procurar a seguinte div:

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

E adicionar a classe "js-subtotal-price".

2. Na pasta snipplets, vamos procurar o arquivo relacionado ao item de produto nos listados neste caso, item.tpl, e buscar a seguinte condição que permite incluir o formulário de 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">
     .....

E substituir essa condição por algo como o seguinte:

{% 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 
%}

Essa alteração garante que, se um produto for apto para assinatura, o usuário será enviado para a página de detalhes do produto, onde poderá escolher essa modalidade de compra.

Nesse mesmo arquivo, vamos incluir o componente da mensagem que mostra se um produto é apto para assinatura e se possui desconto nessa modalidade (recomendamos incluí-lo próximo ao contexto do preço do produto).

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

Deveria ficar semelhante a isto:


Os parâmetros que esse componente aceita são os seguintes:

Parâmetros 

Parâmetro Descrição
svg_sprites
Booleano que determina se se usam SVG sprites.

subscription_icon Booleano para permitir usar um ícone na mensagem.
subscription_icon_svg_id Usado para o ID do SVG sprite do ícone.
subscription_custom_icon

Usado caso não se implemente SVG sprites, para aplicar HTML personalizado no ícone.

CSS

Parâmetro Descrição
container Usado para o contêiner da mensagem.
icon Usado para o ícone da mensagem.
text Usado para o contêiner do texto quando se usa um ícone.
value Usado para o valor do desconto, caso exista.

label

Usado para o texto da mensagem (sem incluir o desconto).

Por fim, neste arquivo precisamos adicionar um “falso” botão de “Compra rápida” que, em vez de abri-la, levará o usuário à página do produto. Para fazer essa alteração, é preciso localizar o seguinte trecho de código que contém os botões 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 %}

E substituí-lo pelo seguinte:

{# 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>

A parte mais relevante é onde abre a condicional {% if product.isSubscribable() %}, que inclui dentro o novo botão que leva à página do produto somente se ele for apto para assinatura.

3. Em seguida, precisamos adicionar as alterações no formulário de produto dentro do arquivo product-form.tpl, que é usado na página de detalhes do produto.

Primeiro, vamos adicionar a classe "js-price-container" ao div que contém os preços do produto (preço base e preço promocional).

Por fora dessa div e logo abaixo, vamos incluir o componente de preço por assinatura.

{{ 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',
    },
}) }}

Deveria ficar semelhante a isto:

Os parâmetros que esse componente aceita são os seguintes:

Parâmetros 

Parâmetro Descrição
subscription_discount_position Usado para posicionar a mensagem de desconto acima dos preços (usando "above"), abaixo (usando "below") ou ao lado (usando "inline"). O padrão é abaixo.

CSS

Parâmetro Descrição
container Usado para o contêiner geral.
discount Usado para o contêiner da mensagem de desconto.
discount_value Usado para a porcentagem de desconto.
label Usado para o texto do desconto.

discount_container

Usado para o contêiner principal da mensagem de desconto.
prices_container Usado para o contêiner dos preços (preço base e preço com desconto).
price_compare Usado para o preço riscado quando existe um desconto.
price_with_subscription

Usado para o preço final por assinatura.

Agora precisamos buscar o elemento que mostra a mensagem de frete grátis a partir de um valor mínimo. Esse geralmente tem a classe "free-shipping-message" e está dentro do seguinte condicional:

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

E adicionamos a classe "js-free-shipping-minimum-message", deixando assim

{% 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 %}

Da mesma forma, buscamos o elemento com a mensagem "Genial! Você tem frete grátis" e adicionamos a classe "js-product-form-free-shipping-message", ficando da seguinte 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, logo acima do botão de compra, colocamos o componente que permite escolher entre 2 modalidades de compra: Compra única e Compra por assinatura.

{{ 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" }),
}) }}

Deveria ficar semelhante a isto:

Os parâmetros que esse componente aceita são os seguintes:

Parámetros 

Parâmetro Descrição
svg_sprites Booleano que determina se se usam SVG sprites.
dropdown_icon Booleano para permitir usar um ícone no dropdown que permite escolher as opções dentro da modalidade de assinatura.
dropdown_icon_svg_id Usado para o ID do SVG sprite do dropdown.
dropdown_custom_icon Usado caso não se implemente SVG sprites, para aplicar HTML personalizado no ícone do dropdown.
cart_alert_icon Booleano usado para o ícone na mensagem que avisa que os produtos do carrinho serão perdidos caso se avance com a assinatura.
cart_alert_icon_svg_id Usado para o ID do SVG na mensagem sobre os produtos no carrinho.
cart_alert_custom_icon Usado caso não se implemente SVG sprites, para aplicar HTML personalizado no ícone da mensagem sobre os produtos no carrinho.
shipping_message_icon Booleano usado para o ícone na mensagem que avisa que as opções de envio serão escolhidas no checkout.
shipping_message_icon_svg_id Usado para o ID do SVG sprite da mensagem sobre as opções de envio.
shipping_message_custom_icon Usado caso não se implemente SVG sprites, para aplicar HTML personalizado no ícone da mensagem sobre as opções de envio.

CSS

Parâmetro Descrição
container Usado para o contêiner geral.
purchase_option_info_container Usado para o contêiner das informações no radio button de cada modalidade de compra.
purchase_option_name Usado para o nome da modalidade de compra em cada radio button.
purchase_option_discount Usado para o desconto no radio button com a modalidade de compra por assinatura.

purchase_option_price

Usado para o preço da modalidade de compra em cada radio button.
purchase_option_single_frequency Usado para o texto que mostra o tempo no radio button com a modalidade de compra por assinatura quando só tem 1 frequência disponível.
purchase_option_price_subscription Usado para o preço riscado quando existe um desconto.
dropdown_option_info Usado para a informação (frequência e desconto) em cada opção de assinatura dentro do menu suspenso.
dropdown_option_frequency Usado para a informação do tempo em cada opção de assinatura dentro do dropdown.
dropdown_option_discount Usado para o desconto em cada opção de assinatura dentro do dropdown suspenso.
dropdown_option_price Usado para o preço em cada opção de assinatura dentro do dropdown.
cart_alert Usado para a mensagem que comunica que os produtos no carrinho serão removidos ao avançar com a compra por assinatura.
cart_alert_icon Usado para o ícone da mensagem que comunica que os produtos no carrinho serão removidos ao avançar com a compra por assinatura.
cart_alert_text Usado para o texto da mensagem que comunica que os produtos no carrinho serão removidos ao avançar com a compra por assinatura.
shipping_message Usado para a mensagem que comunica que as opções de envio serão escolhidas no checkout.
shipping_message_title_container Usado para o contêiner do título na mensagem que comunica que as opções de envio serão escolhidas no checkout, caso seja usado um ícone.
shipping_message_icon Usado para o ícone na mensagem que comunica que as opções de envio serão escolhidas no checkout.
shipping_message_title Usado para o título na mensagem que comunica que as opções de envio serão escolhidas no checkout.
shipping_message_text Usado para o texto na mensagem que comunica que as opções de envio serão escolhidas no checkout.
legal_message Usado para a mensagem de termos e condições sobre a compra por assinatura.
legal_link Usado para os links na mensagem de termos e condições sobre a compra por assinatura.
legal_modal Usado para os modals de termos e condições sobre a compra por assinatura.
legal_modal_header Usado para o cabeçalho nos modals de termos e condições sobre a compra por assinatura.
legal_modal_title Usado para o título nos modals de termos e condições sobre a compra por assinatura.
legal_modal_close_icon Usado para o ícone de fechamento nos modals de termos e condições sobre a compra por assinatura.
legal_modal_close_button Usado para o botão de fechamento nos modals de termos e condições sobre a compra por assinatura.
legal_modal_body Usado para o corpo nos modals de termos e condições sobre a compra por assinatura.
legal_modal_overlay Usado para o overlay nos modals de termos e condições sobre a compra por assinatura.
legal_modal_details_title Usado nos subtítulos para o conteúdo dos modals de termos e condições sobre a compra por assinatura.
legal_modal_details_paragraph Usado nos parágrafos para o conteúdo dos modals de termos e condições sobre a compra por assinatura.
legal_modal_details_link

Usado nos links dentro do conteúdo dos modals de termos e condições sobre a compra por assinatura.

Último passo opcional: se o seu design usa o arquivo button-placeholder.tpl e a transição ao adicionar um produto ao carrinho mostra o texto "Adicionando...", precisamos adicionar a classe "js-addtocart-adding-text" à div que tem a classe "js-addtocart-adding", ficando da seguinte forma:

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

Ter adicionado helper classes em seu layout. Você pode seguir este pequeno tutorial para fazer isso (é só copiar e colar algumas classes, não leva mais que 1 minuto).

1. Adicionar os estilos dentro do arquivo static/style-critical.tpl

Se no seu tema você usa uma folha de estilos para o CSS crítico, vamos precisar do seguinte código dentro dela.

.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 desse mesmo arquivo, vamos adicionar os estilos relacionados ao componente de radio button. Caso você os tenha em outro arquivo de CSS, devemos trazê-los para o crítico, já que agora o componente de radio button passa a ser mais 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. Adicionar os estilos dentro do arquivo static/style-async.tpl

Se no seu design você usa uma folha de estilos para CSS assíncrono, vamos precisar do seguinte código dentro dela. Mas, se não for o caso, então você pode adicioná-lo na sua folha principal de CSS.

{# /* // 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 o CSS, vamos adicionar os seguintes estilos no arquivo style-colors.scss.tpl ou onde você tiver os estilos relacionados às cores da loja:

.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. O JavaScript precisamos adicioná-lo no arquivo store.js.tpl, dentro da função changeVariant, ao final dela:

LS.subscriptionChangeVariant(variant);

2. Procuramos o momento em que um produto é adicionado ao carrinho:

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

E logo abaixo da seguinte parte:

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

Adicionamos 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;
}

Isso fará com que, ao comprar um produto por assinatura, o usuário seja direcionado diretamente ao checkout com esse produto.

Ativação

Por enquanto, a ativação será aplicada pela equipe da Nuvemshop, então não hesite em nos contatar assim que terminar de aplicar essas mudanças em storefronts@tiendanube.com

Pronto, agora você já tem a funcionalidade aplicada no seu design. Excelente!