En este tutorial vamos a ver cómo agregar la posibilidad de calcular envío para otros países sin necesidad de cambiar de moneda o idioma.
Vale la pena explicar que en las tiendas existen dos tipos de entidades por cada país configurado desde la pantalla de Configuraciones > Idiomas y Monedas.
- storefront_country: Es el país que define el idioma y la moneda.
- shipping_country: Es el país al cual se envía la compra (se puede elegir desde un desplegable en el paso 1 del checkout)
En todas las tiendas storefront_country es igual a shipping_country, por ende si el usuario cambia de idioma en una tienda, por ejemplo a Brasil, el país de envío también será Brasil.
En los siguientes pasos vamos a ver como hacer que el shipping_country sea independiente del storefront_country, por ende el usuario va a poder comprar mirando la tienda en su versión Argentina pero enviando a otro país sin necesidad de cambiar el storefront_country.
HTML
1. Lo primero que vamos a hacer es agregar el siguiente código para el texto que dice “Medios de envío para [País de envío]” en el snipplet shipping-calculator.tpl dentro de la carpeta snipplets/shipping. Debemos reemplazar el el texto que dice “Medios de envío” por lo siguiente:
{% if languages | length > 1 %} {{ ' para ' | translate }} {% for language in languages %} {% if (shipping_country_id and language.id == shipping_country_id) or (not shipping_country_id and language.active) %} <a href="#" data-toggle="#{% if product_detail %}product{% else %}cart{% endif %}-shipping-country" class="js-modal-open js-shipping-country-label btn-link btn-link-primary text-capitalize"> {{ language.country_name }} </a> {% endif %} {% endfor %} {% endif %}
Y luego al final de este archivo vamos a agregar el modal que permite el cambio de país:
{# Shipping country modal #} {% if languages | length > 1 %} {% if product_detail %} {% set country_modal_id = 'product-shipping-country' %} {% else %} {% set country_modal_id = 'cart-shipping-country' %} {% endif %} {% embed "snipplets/modal.tpl" with{modal_id: country_modal_id, modal_class: 'bottom modal-centered-small js-modal-shipping-country', modal_position: 'center', modal_transition: 'slide', modal_header: true, modal_footer: true, modal_width: 'centered', modal_zindex_top: true, modal_mobile_full_screen: false} %} {% block modal_head %} {{ 'País de entrega' | translate }} {% endblock %} {% block modal_body %} {% embed "snipplets/forms/form-select.tpl" with{select_label: true, select_label_name: 'País donde entregaremos tu compra' | translate, select_aria_label: 'País donde entregaremos tu compra' | translate, select_custom_class: 'js-shipping-country-select', select_group_custom_class: 'mt-4' } %} {% block select_options %} {% for language in languages %} <option value="{{ language.id }}" data-country-url="{{ language.url }}" {% if (shipping_country_id and language.id == shipping_country_id) or (not shipping_country_id and language.active) %}selected{% endif %}>{{ language.country_name }}</option> {% endfor %} {% endblock select_options%} {% endembed %} {% endblock %} {% block modal_foot %} <a href="#" class="js-save-shipping-country js-modal-close btn btn-primary float-right">{{ 'Aplicar' | translate }}</a> {% endblock %} {% endembed %} {% endif %}
2. En caso que no tengamos un componente modal.tpl para los pop ups, vamos a necesitar crearlo dentro de la carpeta snipplets, usando el siguiente código:
{# /*============================================================================ #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 {% if modal_mobile_full_screen %}js-fullscreen-modal{% endif %} 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 }}" {% if modal_form_hook %}data-store="{{ modal_form_hook }}"{% endif %}> {% endif %} <div class="js-modal-close {% if modal_mobile_full_screen %}js-fullscreen-modal-close{% endif %} 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>
3. En caso de no tener un snipplet form-select.tpl para el componente select, vamos a tener que crearlo dentro de la carpeta snipplets/forms ya que lo usamos en shipping-calculator.tpl durante el paso 1. Este snipplet debe tener el siguiente código:
{# /*============================================================================ #Form select ==============================================================================*/ #Properties #Group //select_group_custom_class for custom CSS classes #Label // select_label_name for name // select_label_id for ID // select_for for label for // select_label_custom_class for custom CSS classes #Select // select_id for id // select_name for name // select_custom_class for custom CSS classes // input_rows for textarea rows // select_options to insert select options // select_aria_label for aria-label attribute #} <div class="form-group {{ select_group_custom_class }}"> {% if select_label %} <label {% if select_label_id%}id="{{ select_label_id }}"{% endif %} class="form-label {{ select_label_custom_class }}" {% if select_for %}for="{{ select_for }}"{% endif %}>{{ select_label_name }}</label> {% endif %} <select {% if select_id %}id="{{ select_id }}"{% endif %} class="form-select {{ select_custom_class }} {% if select_inline %}form-control-inline{% endif %}" {% if select_data %}data-{{select_data}}="{{select_data_value}}"{% endif %} {% if select_name %}name="{{ select_name }}"{% endif %} {% if select_aria_label %}aria-label="{{ select_aria_label }}"{% endif %}> {% block select_options %} {% endblock select_options %} </select> <div class="form-select-icon"> {% include "snipplets/svg/chevron-down.tpl" with {svg_custom_class: "icon-inline icon-w-14 icon-lg svg-icon-text"} %} </div> </div>
4. Por último para la parte de HTML, dentro de la carpeta snipplets/SVG vamos sumar los SVGs que usamos para el select, el icono de filtrado y el modal.
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>
chevron-left.tpl
<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 512"><path d="M231.293 473.899l19.799-19.799c4.686-4.686 4.686-12.284 0-16.971L70.393 256 251.092 74.87c4.686-4.686 4.686-12.284 0-16.971L231.293 38.1c-4.686-4.686-12.284-4.686-16.971 0L4.908 247.515c-4.686 4.686-4.686 12.284 0 16.971L214.322 473.9c4.687 4.686 12.285 4.686 16.971-.001z"/></svg>
chevron-down.tpl
<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M441.9 167.3l-19.8-19.8c-4.7-4.7-12.3-4.7-17 0L224 328.2 42.9 147.5c-4.7-4.7-12.3-4.7-17 0L6.1 167.3c-4.7 4.7-4.7 12.3 0 17l209.4 209.4c4.7 4.7 12.3 4.7 17 0l209.4-209.4c4.7-4.7 4.7-12.3 0-17z"/></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:
{# /* // Mixins */ #} {# This mixin adds browser prefixes to a CSS property #} @mixin prefix($property, $value, $prefixes: ()) { @each $prefix in $prefixes { #{'-' + $prefix + '-' + $property}: $value; } #{$property}: $value; } {# /* // Links */ #} a { color: $main-foreground; fill: $main-foreground; @include prefix(transition, all 0.4s ease, webkit ms moz o); &:hover, &:focus{ color: rgba($main-foreground, .5); fill: rgba($main-foreground, .5); } } .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); } } {# /* // Modals */ #} .modal{ color: $main-foreground; background-color:$main-background; } {# /* // Forms */ #} input, textarea { font-family: $body-font; } .form-control { display: block; padding: 8px; width: 100%; font-size: 16px; /* Hack to avoid autozoom on IOS */ border: 0; border-bottom: 1px solid rgba($main-foreground, .5); -webkit-appearance: none; -moz-appearance: none; appearance: none; color: $main-foreground; background-color: $main-background; &:focus{ outline: 0; } &-inline{ display: inline; } } .form-control::-webkit-input-placeholder { color: $main-foreground; } .form-control:-moz-placeholder { color: $main-foreground; } .form-control::-moz-placeholder { color: $main-foreground; } .form-control:-ms-input-placeholder { color: $main-foreground; } .form-select{ display: block; padding: 10px 0; width: 100%; font-size: 16px; /* Hack to avoid autozoom on IOS */ border: 0; border-bottom: 1px solid rgba($main-foreground, .5); border-radius: 0; -webkit-appearance: none; -moz-appearance: none; appearance: none; color: $main-foreground; background-color: $main-background; @extend %body-font; &-icon{ background: $main-background; } }
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 agregar el siguiente código debajo dentro de la misma, pero si no es el caso podés unificar el CSS de los pasos 2 y 3 en un solo archivo.
{# /* // Forms */ #} .form-group { position: relative; width: 100%; } .form-group .form-select-icon{ position: absolute; bottom: 12px; right: 0; pointer-events: none; }
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 agregar el siguiente código dentro de la misma, pero si no es el caso podés unificar el CSS de los pasos 2 y 3 en un solo archivo.
{# /* // Mixins */ #} {# This mixin adds browser prefixes to a CSS property #} @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 0; clear: both; } &-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%, -50%), webkit ms moz o); .modal-body{ min-height: 150px; max-height: 400px; overflow: auto; } } } &-top.modal-show, &-bottom.modal-show { top: 0; &.modal-centered-small{ top: 50%; } } &-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; 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; &.modal-zindex-top{ z-index: 20000; } } {# /* // Forms */ #} .form-group{ @extend %element-margin; .form-label{ float: left; width: 100%; margin-bottom: 10px; } .alert{ margin: 10px 0 0 0; } } .form-select { display: block; width: 100%; &:focus{ outline:0; } &::-ms-expand { display: none; } } {#/*============================================================================ #Media queries ==============================================================================*/ #} {# /* // Min width 768px */ #} @media (min-width: 768px) { {# /* //// Components */ #} {# /* Modals */ #} .modal{ &-centered{ height: 80%; width: 80%; left: 10%; margin: 5% auto; &-small{ left: 50%; width: 30%; height: auto; max-height: 80%; margin: 0; } } &-docked-md{ width: 500px; &-centered{ left: calc(50% - 250px); bottom: auto; height: auto; } } &-bottom-sheet { top: 100%; &.modal-show { top: 0; bottom: auto; } } &-docked-small{ width: 350px; } }
JS
⚠️ A partir del día 30 de enero de 2023, la librería jQuery será removida del código de nuestras tiendas, por lo tanto la función "$" no podrá ser utilizada.
1. El JavaScript necesitamos agregarlo en el archivo store.js.tpl (o donde tengas tus funciones de JS). El código que necesitamos es el siguiente:
{#/*============================================================================ #Shipping calculator ==============================================================================*/ #} {# /* // Lang select */ #} changeLang = function(element) { var selected_country_url = element.find("option").filter((el) => el.selected).attr("data-country-url"); location.href = selected_country_url; }; jQueryNuvem(document).on("click", ".js-save-shipping-country", function(e) { e.preventDefault(); {# Change shipping country #} lang_select_option = jQueryNuvem(this).closest(".js-modal-shipping-country"); changeLang(lang_select_option); jQueryNuvem(this).text('{{ "Aplicando..." | translate }}').addClass("disabled"); }); {#/*============================================================================ #Modals ==============================================================================*/ #} {# Full screen mobile modals back events #} if (window.innerWidth < 768) { {# Clean url hash function #} cleanURLHash = function(){ const uri = window.location.toString(); const clean_uri = uri.substring(0, uri.indexOf("#")); window.history.replaceState({}, document.title, clean_uri); }; {# Go back 1 step on browser history #} goBackBrowser = function(){ cleanURLHash(); history.back(); }; {# Clean url hash on page load: All modals should be closed on load #} if(window.location.href.indexOf("modal-fullscreen") > -1) { cleanURLHash(); } {# Open full screen modal and url hash #} jQueryNuvem(document).on("click", ".js-fullscreen-modal-open", function(e) { e.preventDefault(); var modal_url_hash = jQueryNuvem(this).data("modalUrl"); window.location.hash = modal_url_hash; }); {# Close full screen modal: Remove url hash #} jQueryNuvem(document).on("click", ".js-fullscreen-modal-close", function(e) { e.preventDefault(); goBackBrowser(); }); {# Hide panels or modals on browser backbutton #} window.onhashchange = function() { if(window.location.href.indexOf("modal-fullscreen") <= -1) { {# Close opened modal #} if(jQueryNuvem(".js-fullscreen-modal").hasClass("modal-show")){ {# Remove body lock only if a single modal is visible on screen #} if(jQueryNuvem(".js-modal.modal-show").length == 1){ jQueryNuvem("body").removeClass("overflow-none"); } var $opened_modal = jQueryNuvem(".js-fullscreen-modal.modal-show"); var $opened_modal_overlay = $opened_modal.prev(); $opened_modal.removeClass("modal-show"); setTimeout(() => $opened_modal.hide(), 500); $opened_modal_overlay.fadeOut(500); } } } } jQueryNuvem(document).on("click", ".js-modal-open", function(e) { e.preventDefault(); var modal_id = jQueryNuvem(this).data('toggle'); var $overlay_id = jQueryNuvem('.js-modal-overlay[data-modal-id="' + modal_id + '"]'); if (jQueryNuvem(modal_id).hasClass("modal-show")) { let modal = jQueryNuvem(modal_id).removeClass("modal-show"); setTimeout(() => modal.hide(), 500); } else { {# Lock body scroll if there is no modal visible on screen #} if(!jQueryNuvem(".js-modal.modal-show").length){ jQueryNuvem("body").addClass("overflow-none"); } $overlay_id.fadeIn(400); jQueryNuvem(modal_id).detach().appendTo("body"); $overlay_id.detach().insertBefore(modal_id); jQueryNuvem(modal_id).show().addClass("modal-show"); } }); jQueryNuvem(document).on("click", ".js-modal-close", function(e) { e.preventDefault(); {# Remove body lock only if a single modal is visible on screen #} if(jQueryNuvem(".js-modal.modal-show").length == 1){ jQueryNuvem("body").removeClass("overflow-none"); } var $modal = jQueryNuvem(this).closest(".js-modal"); var modal_id = $modal.attr('id'); var $overlay_id = jQueryNuvem('.js-modal-overlay[data-modal-id="#' + modal_id + '"]'); $modal.removeClass("modal-show"); setTimeout(() => $modal.hide(), 500); $overlay_id.fadeOut(500); {# Close full screen modal: Remove url hash #} if ((window.innerWidth < 768) && (jQueryNuvem(this).hasClass(".js-fullscreen-modal-close"))) { goBackBrowser(); } }); jQueryNuvem(document).on("click", ".js-modal-overlay", function(e) { e.preventDefault(); {# Remove body lock only if a single modal is visible on screen #} if(jQueryNuvem(".js-modal.modal-show").length == 1){ jQueryNuvem("body").removeClass("overflow-none"); } var modal_id = jQueryNuvem(this).data('modalId'); let modal = jQueryNuvem(modal_id).removeClass("modal-show"); setTimeout(() => modal.hide(), 500); jQueryNuvem(this).fadeOut(500); if (jQueryNuvem(this).hasClass("js-fullscreen-overlay") && (window.innerWidth < 768)) { cleanURLHash(); } });
Traducciones
En este paso agregamos los textos para las traducciones en el archivo config/translations.txt
es "Aplicar" pt "Aplicar" en "Apply" es_mx "Aplicar" es "Aplicando..." pt "Aplicando..." en "Aplying..." es_mx "Aplicando..." es "No encontramos este código postal{1}" pt "Não conseguimos encontrar esse CEP{1}" en "We couldn't find this zipcode{1}" es_mx "No encontramos este código postal{1}" es " para " pt " para " en " for " es_mx " para " es ". ¿Está bien escrito?" pt ". Está bem escrito?" en ". Is it written right?" es_mx ". ¿Está bien escrito?" es ". Podés intentar con otro o" pt ". Pode tentar outro ou" en ". You can try another or" es_mx ". Puedes intentar con otro o" es "cambiar tu país de entrega" pt "alterar o país de entrega" en "change your shipping country" es_mx "cambiar tu país de entrega" es "País de entrega" pt "Pais de entrega" en "Shipping country" es_mx "País de entrega" es "País donde entregaremos tu compra" pt "País onde entregaremos seu pedido" en "Country where we will deliver your order" es_mx "País donde entregaremos tu compra" es "No encontramos este código postal. ¿Está bien escrito?" pt "Não conseguimos encontrar esse CEP. Está bem escrito?" en "We couldn't find this zipcode. Is it written right?" es_mx "No encontramos este código postal. ¿Está bien escrito?" es "No encontramos este código postal para " pt "Não conseguimos encontrar esse CEP para" en "We couldn't find this zipcode for" es_mx "No encontramos este código postal para"
Activación
Por último recordá que para tener más de un idioma en tu tienda debes hacerlo desde la parte de “Configuraciones/Idiomas y monedas”.