npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@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

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

  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;