4.8.8 • Published 6 months ago

@the-oz/app-sales v4.8.8

Weekly downloads
-
License
ISC
Repository
-
Last release
6 months ago

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.

TypeNameDescription
attrdata-vp-handleAttribute to place the product handle in
classvp-original-pricesClass to use to hide original price
classvp-pricesClass to gather all the VP prices. Make sure to style="display: none;"
classvp-prices-minClass for the minimum price
classvp-prices-maxClass for the maximum price
classvp-prices-discountClass for the discounted price
classvp-prices-absolute-discountClass 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>
<!-- ... -->
TypeNameDescription
attributedata-vp-idAttribute for the product id
attributedata-vp-qtyAttribute for the product quantity
classvp-original-pricesClass to use to hide original price
classvp-pricesWrapper class for all the VP prices. Make sure to add style="display: none;"
classvp-prices-minClass for the lowest price
classvp-prices-maxClass for the highest price
classvp-prices-discountClass for the discounted amount (eg. -10%)
classvp-prices-absolute-discountClass to show absolute discount (eg. -5€) rather than %
classvp-prices-discount-tagsClass for the line item discount tags (eg. VP_AH16 or 10€)
classvp-prices-compare-at-priceClass for the original price per item (not multiplied by quantity)
classvp-prices-final-priceClass 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>
TypeNameDescription
classvp-cart-original-totalClass to use to hide original cart price
classvp-cart-total-containerClass to use to wrap new totals in. Make sure to style="display: none;"
classvp-prices-discount-tagsWill be populated with cart-level discount codes
classvp-prices-coupon-tagsWill be populated with removable coupons (entered by customer)
classvp-cart-totalClass added to an empty element to receive the new total calculated based on VPs.
classvp-cart-compare-atClass for the compare_at_price (the initial non-discounted price)
classvp-cart-total-discount-with-compare-atClass for the total_discount_with_compare_at (= the amount saved in total)
classvp-cart-total-discountClass for the total_discount (= the amount saved through cart level discounts)
classvp-prices-discount-tagsClass 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
TypeNameDescription
classvp-checkout-btnMain 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;
4.8.8

6 months ago

4.8.7

6 months ago

4.8.5

12 months ago

4.8.4

12 months ago

4.8.6

12 months ago

4.8.1

1 year ago

4.8.0

1 year ago

4.8.3

12 months ago

4.8.2

1 year ago

4.7.1

1 year ago

4.6.4

1 year ago

4.7.0

1 year ago

4.6.3-RC.1

2 years ago

4.6.3

2 years ago

4.6.2-RC.1

2 years ago

4.6.2-RC.2

2 years ago

4.6.2

2 years ago

4.6.1

2 years ago

4.5.5

2 years ago

4.5.5-3

2 years ago

4.5.5-4

2 years ago

4.5.5-1

2 years ago

4.5.5-2

2 years ago

4.6.0

2 years ago

4.0.0-RC.1

3 years ago

4.0.0-RC.2

3 years ago

4.0.0-RC.3

3 years ago

4.5.0-RC.10

3 years ago

4.5.3

3 years ago

4.5.0-RC.11

3 years ago

4.5.0-RC.12

3 years ago

4.5.0-RC.13

3 years ago

4.5.0-RC.9

3 years ago

4.5.0-RC.12.1.3

3 years ago

4.0.0-RC.O

3 years ago

4.5.0

3 years ago

4.5.2

3 years ago

4.5.0-RC.12.2

3 years ago

4.5.0-RC.12.1

3 years ago

4.5.0-RC.5.0.0.1

3 years ago

4.5.0-RC.4

3 years ago

4.5.0-RC.3

3 years ago

4.5.0-RC.2

3 years ago

4.5.0-RC.1

3 years ago

4.5.0-RC.8

3 years ago

4.5.0-RC.7

3 years ago

4.5.0-RC.5

3 years ago

4.2.3

3 years ago