@the-oz/app-sales

4.7.1 • Public • Published

Readme

Here is the HOW TO/documentation for adding private sales (VP = "Ventes Privées") on the client website.

Checklist VP

Go to the VP checklist.

Intro

You have to keep the same classes/attributes names as described in this document otherwise the VPs won't work. The way it has been generalised allows developers to add a few classes/attributes and HTML elements into the existing code to activate the VP module.

Developers then add a CSS layer to style the generic classes.

Follow the setup first and then start adding classes/attributes and HTML as described below.

IMPORTANT: DiscountNinja will be disabled for connected users during the VPs as it isn't compatible with the VP module.

1. Include the VP files to the project

  1. Copy and paste the snippet file app_sales.liquid (from the doc/shopify folder) into the snippets folder of the destination site.
  2. Render the snippet app_salesin the <head> of theme.liquid: IMPORTANT It must be included AFTER any DiscountNinja related scripts.
<head>
  <!-- ... -->
  <script src="{{ 'theme.js' | asset_url }}" defer></script>
   <!-- It's a good idea to render the snippet after `theme.js` -->
   {% render 'app_sales' %}
  <!-- ... -->
</head>

2. Create a window object with Liquid data

  1. Copy-paste the following code "snippet" at the end of settings_schema.json.

  2. Copy-paste FR translations, EN translations and any other relevant language file into fr.json and en.json.

3. Init prices for products with handles (usually on collection page, search page and cross-sells)

Check for app integrations below for USF.

  1. Add the data-vp-handle attribute with the "product_handle" as a value to the product "container class".
  2. Add the vp-original-prices class to the HTML element which contains the original price. It is going to hide when the VPs price will be added.
  3. After the HTML element which contains the original product price, add the following code:
<!-- ... -->
<div class="vp-prices" style="display: none;">
  <span class="vp-prices-from" style="display: none;">{{ 'ope_com.prices.from' | t }}</span>
  <span class="vp-prices-min"></span>
  <span class="vp-prices-max"></span>
  <span class="vp-prices-discount {% if product.tags contains settings.sales_absolute_discounts_tag %}vp-prices-absolute-discount{% endif %}"></span>
</div>
<!-- ... -->

Note: reuse to the same CSS classes to stay consistent.

Example:

<!-- ... -->
<div class="grid__item grid-product" data-vp-handle="{{ product.handle }}">
  <!-- ... -->
  <!-- ORIGINAL PRICE -->
  <div class="grid-product__price vp-original-prices"></div>
  <!-- NEW PRICES (VPs) -->
  <div class="vp-prices" style="display: none;">
    <span class="vp-prices-min"></span>
    <span class="vp-prices-max"></span>
    <span class="vp-prices-discount {% if product.tags contains settings.sales_absolute_discounts_tag %}vp-prices-absolute-discount{% endif %}"></span>
  </div>
  <!-- ... -->
</div>
<!-- ... -->

Remark:

Liquid handles special characters (ex: ©) without escaping them, but JS doesn't (which can be relevant in some implementation of product recommendations for ex.). To keep them properly formatted in product handles for ex., the functions unescape() or decodeURI() can be used.

Type Name Description
attr data-vp-handle Attribute to place the product handle in
class vp-original-prices Class to use to hide original price
class vp-prices Class to gather all the VP prices. Make sure to style="display: none;"
class vp-prices-min Class for the minimum price
class vp-prices-max Class for the maximum price
class vp-prices-discount Class for the discounted price
class vp-prices-absolute-discount Class to show absolute discount (eg. -5€) rather than %

4. Init prices for products with variants (usually on product page, cart, quickshop)

  1. Add a data-vp-id attribute with the "product id/variant_id" as a value to the product "container class".
  2. (optional) If the product is in the cart, add the data-vp-qty attribute with the quantity as a value on the same element you've added the data-vp-id attribute to.
  3. Add the vp-original-prices class to the HTML element which contains the original price. It is going to hide when the VPs price will be added.
  4. After the HTML element which contains the original price, add the following code: (adapt product.tags according to whether it's on the product page, cart or quickshop)
<!-- VP PRICES -->
<div class="vp-prices" style="display: none;">
  <span class="vp-prices-min"></span>
  <span class="vp-prices-max"></span>
  <span class="vp-prices-discount {% if product.tags contains settings.sales_absolute_discounts_tag %}vp-prices-absolute-discount{% endif %}"></span>
  <!-- optional -->
</div>
<!-- END VP PRICES -->
  1. (optional) If the product is in the cart, you can add an element for each of these classes (doc below) inside the element with the vp-prices class.
    1. vp-prices-discount (will be populated with the discounted amount (eg. -10%) or the absolute discounted amount (eg. -5€) if the vp-prices-absolute-discount class is added)
    2. vp-prices-discount-tags (will be populated with the line item discount tags (eg. VP_AH16 or 10€))
    3. vp-prices-compare-at-price (will be populated with the compare at price per item (not multiplied by quantity))
    4. vp-prices-final-price (will be populated with the final price per item (not multiplied by quantity))

Note: reuse the same CSS classes to stay consistent.

Exemple (in the cart/mini cart):

<!-- ... -->
<div class="cart__row cart__product-grid" data-vp-id="{{item.id}}" data-vp-qty="{{item.quantity}}">
  <!-- ... -->
  <!-- ORIGINAL PRICE -->
  <span class="cart__price vp-original-prices">{{ line_item_price | money }}</span>
  <!-- NEW PRICES (VPs) -->
  <div class="vp-prices" style="display: none;">
    <span class="vp-prices-min"></span>
    <span class="vp-prices-max"></span>
    <span class="vp-prices-discount {% if item.product.tags contains settings.sales_absolute_discounts_tag %}vp-prices-absolute-discount{% endif %}"></span>
    <!-- optional -->
    <span class="vp-prices-discount-tags"></span>
    <!-- optional -->
    <span class="vp-prices-compare-at-price"></span>
    <!-- optional -->
    <span class="vp-prices-final-price"></span>
    <!-- optional -->
  </div>
  <!-- ... -->
</div>
<!-- ... -->
Type Name Description
attribute data-vp-id Attribute for the product id
attribute data-vp-qty Attribute for the product quantity
class vp-original-prices Class to use to hide original price
class vp-prices Wrapper class for all the VP prices. Make sure to add style="display: none;"
class vp-prices-min Class for the lowest price
class vp-prices-max Class for the highest price
class vp-prices-discount Class for the discounted amount (eg. -10%)
class vp-prices-absolute-discount Class to show absolute discount (eg. -5€) rather than %
class vp-prices-discount-tags Class for the line item discount tags (eg. VP_AH16 or 10€)
class vp-prices-compare-at-price Class for the original price per item (not multiplied by quantity)
class vp-prices-final-price Class for the the final price per item (not multiplied by quantity)

5. Init cart total

  1. Wrap the current total prices container with the vp-cart-original-total class.
  2. After that HTML element, add the following code:
<div class="vp-cart-total-container" style="display:none;">
  <!-- optional -->
  <span class="vp-prices-coupon-tags"></span>
  <!-- optional -->
  <span class="vp-prices-discount-tags"></span>

  <span class="vp-cart-total"></span>

  <span class="vp-cart-compare-at"></span>
  <!-- optional -->
  <span class="vp-cart-total-discount"></span>
  <!-- optional -->
  <span class="vp-cart-total-discount-with-compare-at"></span>
  <!-- optional -->
</div>

Note: reuse to the same CSS classes to stay consistent.

Example :

<div class="Cart__Total Heading u-h6 vp-cart-original-total">
  <span class="cart-total-price">{{ cart.total_price | money_without_trailing_zeros }}</span>
  <span class="init-total-price">{{ initTotalPrice | money_without_trailing_zeros }}</span>
</div>
<div class="Cart__Total Heading u-h6 vp-cart-total-container" style="display:none;">
  <span class="vp-cart-total cart-total-price"></span>
  <span class="vp-cart-compare-at init-total-price"></span>
  <!-- optional -->
  <span class="vp-prices-discount-tags"></span>
  <!-- optional -->
</div>
Type Name Description
class vp-cart-original-total Class to use to hide original cart price
class vp-cart-total-container Class to use to wrap new totals in. Make sure to style="display: none;"
class vp-prices-discount-tags Will be populated with cart-level discount codes
class vp-prices-coupon-tags Will be populated with removable coupons (entered by customer)
class vp-cart-total Class added to an empty element to receive the new total calculated based on VPs.
class vp-cart-compare-at Class for the compare_at_price (the initial non-discounted price)
class vp-cart-total-discount-with-compare-at Class for the total_discount_with_compare_at (= the amount saved in total)
class vp-cart-total-discount Class for the total_discount (= the amount saved through cart level discounts)
class vp-prices-discount-tags Class for the cart discount tags (eg. -30€ or VIP).

6. Init checkout buttons and error messages

  1. Add the vp-checkout-btn class to the button/div which when clicked goes to the checkout.
  2. (optional) Add <p class="vp-error-message"></p> after the checkout form — it will be populated by error messages if there are any.
  3. Don't forget to follow the same steps for mini-carts.
<!-- ... -->
<button
  class="vp-checkout-btn"
  type="submit"
  class="action_button add_to_cart"
  id="checkout"
  name="checkout"
>
  {{ 'cart.general.checkout' | t }}
</button>
<!-- ... -->

7. Add form for coupons

It is possible to add a discount code form on the cart page (for the customer to enter coupons, get feedback on them and have prices updated accordingly).

To have that feature, add this code on the cart page (and drawer if relevant).

{% if settings.sales_enable_coupons %}
<div class="vp-coupons" style="display:none;">
  <div class="vp-coupons-form">
    <input placeholder="Code de réduction" class="vp-coupons-input" type="text" />
    <button type="button" class="vp-coupons-submit">{{ 'ope_com.coupons.apply' | t }}</button>
  </div>
  <div class="vp-coupons-feedback"></div>
</div>
{% endif %}

Note: replace "Appliquer" with a translatable variable.

8. Add form for fidelity coupons

It is possible to add a fidelity coupons form on the cart page (for the customer to select a single fidelity coupon, get feedback and have prices updated accordingly).

To have that feature, add this code on the cart page (and drawer if relevant).

{% if settings.sales_enable_fidelity_coupons %}
<div class="vp-fidelity-select-container" style="display: none">
  <div class="vp-fidelity-select-available">
    <button class="vp-fidelity-submit vp-add-fidelity">{{ 'customer.fidelity.apply' | t }}</button>
  </div>
  <div class="vp-fidelity-select-used">
    <div>
      {{ 'customer.fidelity.has_been_applied' | t }}
    </div>
    <button class="vp-fidelity-submit vp-remove-fidelity">
      {{ 'customer.fidelity.remove' | t }}
    </button>
  </div>
</div>
{% endif %}

9. Refresh VP prices when needed

In some cases (filtering, sorting, infinite scrolling,...) when products are dynamically changing on the page, prices need to be refreshed.

Note: USF search should be handles by default in most cases already.

Calling document.dispatchEvent(new Event("vp-rerender")) will run the VP script again and go over all the non-initialised prices.

Here is an example:

// For example after filtering
// "oz:theme:reinit" is an event example on HW which is dispacth when filters are used
document.addEventListener('oz:theme:reinit', () => {
  document.dispatchEvent(new Event('vp-rerender')); // We dispatch "vp-rerender" which "refresh"/"rerender" the VP
});
DEFINITION
Type Name Description
class vp-checkout-btn Main class to use on the checkout button (just before generating the draft order)

10. Create Lightregister

The lightregister page allows users to get directed to a specific page (setup in the Customize of the theme) once they're logged-in.

  1. In live theme, create a new page liquid template called lightregister
  2. Create a page using that template and publish it
  3. In development theme, create a page.lightregister.liquid file in the template folder and copy paste the content of this template in it.
  4. Add the following SCSS file to your theme

The content of the page can now be administered through the options in the Customize bit of the theme.

11. (annex) - App Integration : USF

App Integration: USF

If the website is using USF, products with handles on the collection page and search pages will need to be edited like so through the app:

  1. Add the data-vp-handle attribute to searchResultsGridViewItem.
searchResultsGridViewItem: `
  /* ... */
  <div class="product " :data-vp-handle="product.urlName">
  /* ... */
`;
  1. Find HTML element which contains the original price and add the vp-original-prices class to it.
  2. After the HTML element which contains the original price, add the following code:
<!-- VP PRICES -->
<div class="vp-prices" style="display: none;">
  <span class="vp-prices-min"></span>
  <span class="vp-prices-max"></span>
  <span class="vp-prices-discount"></span>
  <!-- optional -->
</div>
<!-- END VP PRICES -->
  1. Add the data-vp-handle attribute to searchResultsListViewItem.
searchResultsListViewItem: `
  /* ... */
  <div class="product" :data-vp-handle="product.urlName">
  /* ... */
`;
  1. Repeat Step 2 and Step 3 for this element.

  2. Add the data-vp-handle attribute to instantSearchItem.

instantSearchItem: `
  /* ... */
  <div class="usf-pull-left" :data-vp-handle="product.urlName">
  /* ... */
`;
  1. Repeat Step 2 and Step 3 for this element.

12. (annex) - App Integration : Algolia

  1. In algolia_helpers.js.liquid, add the productHandle function under algolia.helpers:
algolia.helpers = {
  productHandle: function productHandle() {
    return this.handle;
  },
};
  1. In algolia_autocomplete_product.hogan.liquid add the data-vp-handle="[[# helpers.productHandle ]][[/ helpers.productHandle ]]" attribute on the product container
  2. Add the vp-original-prices on the initial price container
  3. Add the vp-prices container under it.

Example:

<div
  data-algolia-index="[[ _index ]]"
  data-algolia-position="[[ _position ]]"
  data-algolia-queryid="[[ queryID ]]"
  data-algolia-objectid="[[ objectID ]]"
  class="aa-product"
>
  <div class="aa-product-picture">
    <img src="[[# helpers.grandeImage ]][[/ helpers.grandeImage ]]" alt="" />
  </div>
  <div
    class="aa-product-text"
    data-vp-handle="[[# helpers.productHandle ]][[/ helpers.productHandle ]]"
  >
    <p class="aa-product-price vp-original-prices">
      [[# helpers.autocompletePrice ]][[/ helpers.autocompletePrice ]]
    </p>
    <p class="vp-prices vp-prices-instant-search" style="display: none;">
      <span class="vp-prices-max"></span>
      <b class="vp-prices-min"></b>
    </p>
    <p class="aa-product-title">[[# helpers.fullHTMLTitle ]][[/ helpers.fullHTMLTitle ]]</p>
    <p class="aa-product-info">[[# meta ]] [[ meta.global.baseline ]] [[/ meta ]]</p>
  </div>
</div>

Notes

  • If something is not working properly, make sure jquery is available (note: soon-to-be-deprecated-as-all-of-jquery-will-be-removed-very-very-soon :-) )

  • If JS is added manually, make sure it has the data-ot-ignore attribute if the destination website has CookiePro installed

Example: <script data-ot-ignore src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

import jquery from 'jquery';

window.$ = window.jQuery = jquery;

Readme

Keywords

none

Package Sidebar

Install

npm i @the-oz/app-sales

Weekly Downloads

26

Version

4.7.1

License

ISC

Unpacked Size

1.23 MB

Total Files

24

Last publish

Collaborators

  • nicolas.bureau
  • jeanchal
  • iohanai
  • yellowbear
  • mathieu-chauvet-theoz
  • victornitu