/**
* @param {HTMLElement} el - product container element
* @param {ProductVariant} variant - the product variant that's selected
*
* You can use the provided Shopyflow objects to implement custom logic.
*/
console.log('RUNNING LOKI PRODUCT.JS - v7');
/**
* This script integrates with Shopyflow to dynamically update product prices,
* calculate per-unit pricing, and manage UI visibility.
* It now responds to both variant changes and selling plan clicks.
*/
window.addEventListener('ShopyflowReady', event => {
// --- STATE MANAGEMENT ---
// We store the current variant in a higher-scope variable so our click handlers can access it.
let currentVariant = null;
// --- ELEMENT SELECTION ---
const recurringPurchaseButton = document.querySelector(
'[sf-purchase-option-value="recurring"]'
);
const singlePurchaseButton = document.querySelector(
'[sf-purchase-option-value="single"]'
);
const sellingPlanContainer = document.querySelector(
'[sf-change-selling-plan]'
);
const buyOncePriceElement = document.querySelector(
'[product="buyonce-price"]'
);
const recurringPriceElement = document.querySelector(
'[product="recurring-price"]'
);
const buyOnceAmountElement = document.querySelector(
'[product="buyonce-amount"]'
);
const recurringAmountElement = document.querySelector(
'[product="recurring-amount"]'
);
const sellingPlanButtons = document.querySelectorAll(
'[sf-selling-plan-value]'
);
// --- REUSABLE CORE FUNCTION ---
/**
* Updates all price and amount elements based on a given variant and an optional selling plan ID.
* @param {object} variant - The currently selected product variant object.
* @param {string|null} targetPlanId - The ID of the selling plan to display prices for. If null, shows one-time purchase price.
*/
function updatePriceDisplay(variant, targetPlanId) {
if (!variant) {
console.log('Price update skipped: no current variant.');
return;
}
console.log(
`Updating prices for variant ${variant.id} and plan ${targetPlanId}`
);
// 1. Parse number of cans from the variant data
let numberOfCans = 0;
if (
Array.isArray(variant.selectedOptions) &&
variant.selectedOptions[0]?.value
) {
const parsedCans = parseInt(variant.selectedOptions[0].value, 10);
if (!isNaN(parsedCans) && parsedCans > 0) {
numberOfCans = parsedCans;
}
}
// 2. Find the correct selling plan allocation
let targetSellingPlanAllocation = null;
if (targetPlanId && Array.isArray(variant.sellingPlanAllocations)) {
targetSellingPlanAllocation = variant.sellingPlanAllocations.find(
alloc =>
alloc.sellingPlan && alloc.sellingPlan.id.endsWith(`/${targetPlanId}`)
);
}
// 3. Update all price and amount elements
if (targetSellingPlanAllocation) {
// --- A Subscription Plan is Active ---
const priceAdjustments = targetSellingPlanAllocation.priceAdjustments[0];
const compareAtPrice = priceAdjustments.compareAtPrice; // The non-discounted price
const perDeliveryPrice = priceAdjustments.perDeliveryPrice; // The discounted subscription price
if (buyOncePriceElement)
buyOncePriceElement.innerText = `$${compareAtPrice.amount}`;
if (recurringPriceElement)
recurringPriceElement.innerText = `$${perDeliveryPrice.amount}`;
if (numberOfCans > 0) {
if (buyOnceAmountElement) {
const pricePerCan = parseFloat(compareAtPrice.amount) / numberOfCans;
buyOnceAmountElement.innerText = `$${pricePerCan.toFixed(2)}/can`;
}
if (recurringAmountElement) {
const pricePerCan =
parseFloat(perDeliveryPrice.amount) / numberOfCans;
recurringAmountElement.innerText = `$${pricePerCan.toFixed(2)}/can`;
}
}
} else {
// --- "Buy Once" is Selected or No Plan Found ---
if (buyOncePriceElement)
buyOncePriceElement.innerText = `$${variant.price.amount}`;
if (recurringPriceElement) recurringPriceElement.innerText = ''; // Clear recurring price
if (buyOnceAmountElement && numberOfCans > 0) {
const pricePerCan = parseFloat(variant.price.amount) / numberOfCans;
buyOnceAmountElement.innerText = `$${pricePerCan.toFixed(2)}/can`;
} else if (buyOnceAmountElement) {
buyOnceAmountElement.innerText = '';
}
if (recurringAmountElement) recurringAmountElement.innerText = ''; // Clear recurring amount
}
}
// --- EVENT LISTENERS ---
// 1. Listener for Toggling UI Visibility
if (recurringPurchaseButton && singlePurchaseButton && sellingPlanContainer) {
recurringPurchaseButton.addEventListener('click', () => {
sellingPlanContainer.style.display = '';
});
singlePurchaseButton.addEventListener('click', () => {
sellingPlanContainer.style.display = 'none';
// When user clicks "One-time purchase", update prices to reflect that.
updatePriceDisplay(currentVariant, null);
});
}
// 2. NEW: Listener for Clicks on Specific Selling Plan Buttons
if (sellingPlanButtons.length > 0) {
sellingPlanButtons.forEach(button => {
button.addEventListener('click', () => {
// Get the plan ID from the button that was just clicked.
const clickedPlanId = button.getAttribute('sf-selling-plan-value');
console.log(`Selling plan button clicked. Plan ID: ${clickedPlanId}`);
// Call the main update function with the currently active variant and the newly clicked plan ID.
updatePriceDisplay(currentVariant, clickedPlanId);
});
});
}
// 3. Listener for Product Variant Changes
Shopyflow.on('optionChange', ({ el, variant }) => {
// First, update the globally accessible currentVariant.
currentVariant = variant;
// Determine the default selling plan for this newly selected variant.
let initialPlanId = null;
try {
const subStateAttr = el.getAttribute('sf-current-subscription-state');
if (subStateAttr) {
const subscriptionState = JSON.parse(subStateAttr);
if (subscriptionState && subscriptionState[variant.id]) {
initialPlanId = subscriptionState[variant.id].sellingPlanId;
}
}
} catch (e) {
console.error('Error parsing sf-current-subscription-state:', e);
}
// Call the main update function to set the initial prices for this variant.
updatePriceDisplay(currentVariant, initialPlanId);
});
});