Last active
July 26, 2023 17:24
-
-
Save peterbrunton/ad8b96294bcbc5cd42a24f97093bae81 to your computer and use it in GitHub Desktop.
Product injection based on cart conditions. GWP offers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This gist creates a gift with purchase function that injects a product or multiple products into the cart when conditions are met. | |
**It is important to mention this function does not perform any discount that works separately current implementation is performed by the Shopify Scripts App | |
*This function is not considered good UX practice. | |
The conditions are cart threshold amount met or/and cart threshold amount met and conditional product applied to the cart. | |
When the conditions are no longer met the products are removed from the cart. | |
Create a new section, sections/gwp-injection.liquid | |
``` | |
{% if section.settings.enable %} | |
<gwp-product-injection class="hidden"> | |
{% for block in section.blocks %} | |
<li | |
class="gwp-product-injection--item" | |
data-variant-id="{{ all_products[block.settings.product].first_available_variant.id }}" | |
data-product-title="{{ all_products[block.settings.product].title }}" | |
data-injection-threshold="{{ block.settings.threshold | append: '00' | times: 1 }}" | |
data-product-image="{{ block.settings.threshold | append: '00' | times: 1 }}" | |
data-discount-title="{{ block.settings.discount_title }}" | |
{% if block.settings.conditional_product %} | |
data-conditional-product="{{ all_products[block.settings.conditional_product].handle }}" | |
{% endif %} | |
> | |
<span>Your free gift, {{ all_products[block.settings.product].title }} has been added to the cart.</span> | |
</li> | |
{% endfor %} | |
</gwp-product-injection> | |
<script defer> | |
if (!customElements.get('gwp-product-injection')) { | |
customElements.define('gwp-product-injection', class GWPProductInjection extends HTMLElement { | |
constructor() { | |
super(); | |
this.GWPs = new Map(); // Store GWPs as a Map | |
this.boundHandleGWP = this.handleGWP.bind(this); | |
document.addEventListener('cart:change', this.boundHandleGWP); | |
document.addEventListener('cart:refresh', this.boundHandleGWP); | |
} | |
connectedCallback() { | |
this.populateGWPs(); | |
this.cart = document.querySelector('cart-notification') || document.querySelector('cart-drawer'); | |
this.cartItemsHandles = new Set(); | |
} | |
populateGWPs() { | |
// Populate GWPs from the list | |
[...document.querySelectorAll('.gwp-product-injection--item')].forEach((GWP) => { | |
const variantId = GWP.dataset.variantId; | |
this.GWPs.set(variantId, { | |
threshold: Number(GWP.dataset.injectionThreshold), | |
conditionalProduct: GWP.dataset.conditionalProduct, | |
added: false, | |
variantId: variantId | |
}); | |
}); | |
} | |
setStatusGWPProductInjection(varId, status) { | |
const gwp = this.GWPs.get(varId); | |
if (gwp) { | |
gwp.added = status; | |
} | |
} | |
getStatusGWPProductInjection(varId) { | |
const gwp = this.GWPs.get(varId); | |
return gwp ? gwp.added : false; | |
} | |
disconnectedCallback() { | |
document.removeEventListener('cart:change', this.boundHandleGWP); | |
} | |
async getCurrentCartData() { | |
let fetchAdmin = { | |
method: 'GET', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
}; | |
let data = await fetch('/cart.js', fetchAdmin).then((response) => response.json()); | |
return data; | |
} | |
async fireGWP(var_id) { | |
this.setStatusGWPProductInjection(var_id, true); | |
const body = JSON.stringify({ | |
items: [ | |
{ | |
id: var_id, | |
quantity: 1, | |
}, | |
], | |
}); | |
await fetch(`${Shopify.routes.root}cart/add.js`, { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, | |
body: body, | |
}) | |
.then((response) => response.json()) | |
.then((parsedState) => { | |
document.documentElement.dispatchEvent(new CustomEvent('theme:loading:start', { bubbles: true })); | |
document.documentElement.dispatchEvent(new CustomEvent('cart:refresh', { bubbles: true })); | |
document.documentElement.dispatchEvent(new CustomEvent('theme:loading:end', { bubbles: true })); | |
}) | |
.catch((e) => { | |
console.error(e); | |
this.dispatchEvent(new CustomEvent('cart:error', { bubbles: true })); | |
}) | |
.finally(() => { | |
this.handleGWP(); // Check for other eligible GWPs after adding one | |
}); | |
} | |
async removeGWP(var_id) { | |
this.setStatusGWPProductInjection(var_id, false); | |
const body = JSON.stringify({ | |
quantity: 0, | |
id: var_id, | |
}); | |
await fetch('/cart/change', { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, | |
body: body, | |
}) | |
.then((response) => response.json()) | |
.then((parsedState) => { | |
document.documentElement.dispatchEvent(new CustomEvent('theme:loading:start', { bubbles: true })); | |
document.documentElement.dispatchEvent(new CustomEvent('cart:refresh', { bubbles: true })); | |
document.documentElement.dispatchEvent(new CustomEvent('theme:loading:end', { bubbles: true })); | |
}) | |
.catch((e) => { | |
console.error(e); | |
}) | |
} | |
isConditionalProductInCart(conditionalProduct, cartData) { | |
if (!conditionalProduct) return true; | |
const cartItems = cartData.items || []; | |
return cartItems.some((item) => item.handle === conditionalProduct); | |
} | |
async handleGWP() { | |
console.log('Handling GWP...'); | |
const cartData = await this.getCurrentCartData(); | |
console.log('Cart Data:', cartData); | |
// Create a copy of the cart items handles for easy lookup | |
this.cartItemsHandles = new Set(cartData.items.map(item => item.handle)); | |
// Check if added GWPs should be removed | |
for (const gwp of Array.from(this.GWPs.values()).filter(gwp => gwp.added)) { | |
if ((gwp.conditionalProduct && !this.cartItemsHandles.has(gwp.conditionalProduct)) || gwp.threshold > cartData.total_price) { | |
// Conditional product is not in the cart anymore, or cart total is below the GWP threshold | |
await this.removeGWP(gwp.variantId); | |
} | |
} | |
// Add all eligible GWPs to the cart | |
for (const [variantId, gwp] of this.GWPs) { | |
if (gwp.added) continue; // Skip the ones already added | |
if ((gwp.conditionalProduct && this.cartItemsHandles.has(gwp.conditionalProduct)) || (!gwp.conditionalProduct && gwp.threshold <= cartData.total_price)) { | |
await this.fireGWP(variantId); | |
} | |
} | |
} | |
}); | |
} | |
</script> | |
{% endif %} | |
{% schema %} | |
{ | |
"name": "t:sections.gwp_injection.name", | |
"tag": "section", | |
"class": "section", | |
"disabled_on": { | |
"groups": ["header", "custom.overlay", "footer"] | |
}, | |
"settings": [ | |
{ | |
"type": "checkbox", | |
"id": "enable", | |
"label": "t:sections.gwp_injection.enable" | |
} | |
], | |
"blocks": [ | |
{ | |
"type": "gwp_item", | |
"name": "t:sections.gwp_injection.blocks.gwp_item.name", | |
"limit": 3, | |
"settings": [ | |
{ | |
"type": "product", | |
"id": "product", | |
"label": "t:sections.gwp_injection.blocks.gwp_item.product" | |
}, | |
{ | |
"type": "text", | |
"id": "threshold", | |
"label": "t:sections.gwp_injection.blocks.gwp_item.threshold" | |
}, | |
{ | |
"type": "text", | |
"id": "discount_title", | |
"label": "t:sections.gwp_injection.blocks.gwp_item.discount" | |
}, | |
{ | |
"type": "product", | |
"id": "conditional_product", | |
"label": "Conditional product", | |
"info": "The gwp is only fired when this product is in the cart" | |
} | |
] | |
} | |
], | |
"presets": [ | |
{ | |
"name": "t:sections.gwp_injection.name", | |
"blocks": [ | |
{ | |
"type": "gwp_item" | |
} | |
] | |
} | |
] | |
} | |
{% endschema %} | |
``` | |
In layout/theme.liquid | |
Add the section to the theme file just inside the body tag | |
```{%- section 'gwp-injection' -%}``` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment