@the-oz/app-sales
v4.8.6
Published
Here is the HOW TO/documentation for adding private sales (VP = "Ventes Privées") on the client website.
Downloads
13
Keywords
Readme
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
- Copy and paste the snippet file
app_sales.liquid
(from the doc/shopify folder) into thesnippets
folder of the destination site. - Render the snippet
app_sales
in the<head>
oftheme.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
Copy-paste the following code "snippet" at the end of
settings_schema.json
.Copy-paste FR translations, EN translations and any other relevant language file into
fr.json
anden.json
.
3. Init prices for products with handles (usually on collection page, search page and cross-sells)
Check for app integrations below for USF.
- Add the
data-vp-handle
attribute with the "product_handle" as a value to the product "container class". - 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. - 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)
- Add a
data-vp-id
attribute with the "product id/variant_id" as a value to the product "container class". - (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 thedata-vp-id
attribute to. - 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. - 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 -->
- (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.vp-prices-discount
(will be populated with the discounted amount (eg.-10%
) or the absolute discounted amount (eg.-5€
) if thevp-prices-absolute-discount
class is added)vp-prices-discount-tags
(will be populated with the line item discount tags (eg.VP_AH16
or10€
))vp-prices-compare-at-price
(will be populated with the compare at price per item (not multiplied by quantity))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
- Wrap the current total prices container with the
vp-cart-original-total
class. - 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
- Add the
vp-checkout-btn
class to the button/div which when clicked goes to the checkout. - (optional) Add
<p class="vp-error-message"></p>
after the checkout form — it will be populated by error messages if there are any. - 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.
- In live theme, create a new page liquid template called
lightregister
- Create a page using that template and publish it
- In development theme, create a
page.lightregister.liquid
file in thetemplate
folder and copy paste the content of this template in it. - 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:
- Add the
data-vp-handle
attribute tosearchResultsGridViewItem
.
searchResultsGridViewItem: `
/* ... */
<div class="product " :data-vp-handle="product.urlName">
/* ... */
`;
- Find HTML element which contains the original price and add the
vp-original-prices
class to it. - 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 -->
- Add the
data-vp-handle
attribute tosearchResultsListViewItem
.
searchResultsListViewItem: `
/* ... */
<div class="product" :data-vp-handle="product.urlName">
/* ... */
`;
Repeat Step 2 and Step 3 for this element.
Add the
data-vp-handle
attribute toinstantSearchItem
.
instantSearchItem: `
/* ... */
<div class="usf-pull-left" :data-vp-handle="product.urlName">
/* ... */
`;
- Repeat Step 2 and Step 3 for this element.
12. (annex) - App Integration : Algolia
- In
algolia_helpers.js.liquid
, add theproductHandle
function underalgolia.helpers
:
algolia.helpers = {
productHandle: function productHandle() {
return this.handle;
},
};
- In
algolia_autocomplete_product.hogan.liquid
add thedata-vp-handle="[[# helpers.productHandle ]][[/ helpers.productHandle ]]"
attribute on the product container - Add the
vp-original-prices
on the initial price container - 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;