# 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](checklist.md).

## 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](/doc/shopify) folder) into the `snippets` folder of the destination site.
2. Render the snippet `app_sales`in the `<head>` of `theme.liquid`:
   **IMPORTANT** It must be included AFTER any DiscountNinja related scripts.

```html
<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"](doc/shopify/settings_schema.json) at the end of `settings_schema.json`.

2. Copy-paste [FR translations](doc/shopify/fr.json), [EN translations](doc/shopify/en.json) 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:

```html
<!-- ... -->
<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**:

```html
<!-- ... -->
<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)

```html
<!-- 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 -->
```

5. _(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):

```html
<!-- ... -->
<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:

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

```html
<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.

```html
<!-- ... -->
<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).

```html
{% 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).

```html
{% 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:

```js
// 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](doc/shopify/page.lightregister.liquid) in it.
4. Add [the following SCSS file](doc/shopify/lightregister.scss) 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`.

```javascript
searchResultsGridViewItem: `
  /* ... */
  <div class="product " :data-vp-handle="product.urlName">
  /* ... */
`;
```

2. Find HTML element which contains the original price and add the `vp-original-prices` **class** to it.
3. After the HTML element which contains the original price, add the following code:

```html
<!-- 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 -->
```

3. Add the `data-vp-handle` **attribute** to `searchResultsListViewItem`.

```javascript
searchResultsListViewItem: `
  /* ... */
  <div class="product" :data-vp-handle="product.urlName">
  /* ... */
`;
```

4. Repeat **Step 2** and **Step 3** for this element.

5. Add the `data-vp-handle` **attribute** to `instantSearchItem`.

```javascript
instantSearchItem: `
  /* ... */
  <div class="usf-pull-left" :data-vp-handle="product.urlName">
  /* ... */
`;
```

5. 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`:

```javascript
algolia.helpers = {
  productHandle: function productHandle() {
    return this.handle;
  },
};
```

2. In `algolia_autocomplete_product.hogan.liquid` add the `data-vp-handle="[[# helpers.productHandle ]][[/ helpers.productHandle ]]"` attribute on the product container
3. Add the `vp-original-prices` on the initial price container
4. Add the `vp-prices` container under it.

Example:

```html
<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>`

```javascript
import jquery from 'jquery';

window.$ = window.jQuery = jquery;
```
