/**
* @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 - v8');
/**
* This script integrates with Shopyflow to dynamically update product prices,
* calculate per-unit pricing, and manage UI visibility.
* v8: Fixes a bug where the recurring price would disappear on "Buy Once" selection.
*/
window.addEventListener('ShopyflowReady', event => {
// --- STATE MANAGEMENT ---
// Store the current variant and its container element for global access by event handlers.
let currentVariant = null;
let currentEl = null; // NEW: Store the container element
// --- 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.
*/
function updatePriceDisplay(variant, targetPlanId) {
if (!variant) return;
let numberOfCans = 0;
if (variant.selectedOptions?.[0]?.value) {
const parsedCans = parseInt(variant.selectedOptions[0].value, 10);
if (!isNaN(parsedCans) && parsedCans > 0) numberOfCans = parsedCans;
}
let targetSellingPlanAllocation = null;
if (targetPlanId && Array.isArray(variant.sellingPlanAllocations)) {
targetSellingPlanAllocation = variant.sellingPlanAllocations.find(alloc =>
alloc.sellingPlan?.id.endsWith(`/${targetPlanId}`)
);
}
// Always update the "Buy Once" price based on the variant's base price or the plan's compareAtPrice.
const oneTimePrice = targetSellingPlanAllocation
? targetSellingPlanAllocation.priceAdjustments[0].compareAtPrice.amount
: variant.price.amount;
if (buyOncePriceElement) buyOncePriceElement.innerText = `$${oneTimePrice}`;
if (buyOnceAmountElement && numberOfCans > 0) {
const pricePerCan = parseFloat(oneTimePrice) / numberOfCans;
buyOnceAmountElement.innerText = `$${pricePerCan.toFixed(2)}/can`;
} else if (buyOnceAmountElement) {
buyOnceAmountElement.innerText = '';
}
// Only update the recurring price if a valid plan is found.
// If not, the existing value remains, preventing it from disappearing.
if (targetSellingPlanAllocation) {
const priceAdjustments = targetSellingPlanAllocation.priceAdjustments[0];
const perDeliveryPrice = priceAdjustments.perDeliveryPrice;
if (recurringPriceElement)
recurringPriceElement.innerText = `$${perDeliveryPrice.amount}`;
if (recurringAmountElement && numberOfCans > 0) {
const pricePerCan = parseFloat(perDeliveryPrice.amount) / numberOfCans;
recurringAmountElement.innerText = `$${pricePerCan.toFixed(2)}/can`;
}
}
// --- FIX #1: The 'else' block that cleared the recurring price is removed. ---
// By removing it, the recurring price will remain visible even when 'Buy Once' is selected.
}
// --- EVENT LISTENERS ---
if (recurringPurchaseButton && singlePurchaseButton && sellingPlanContainer) {
recurringPurchaseButton.addEventListener('click', () => {
sellingPlanContainer.style.display = '';
// --- FIX #2: Re-evaluate and display the correct subscription price immediately. ---
if (currentEl && currentVariant) {
let defaultPlanId = null;
try {
const subStateAttr = currentEl.getAttribute(
'sf-current-subscription-state'
);
if (subStateAttr) {
const subscriptionState = JSON.parse(subStateAttr);
if (subscriptionState?.[currentVariant.id]) {
defaultPlanId =
subscriptionState[currentVariant.id].sellingPlanId;
}
}
} catch (e) {
console.error('Error parsing subscription state on click:', e);
}
// Call the update function to ensure the recurring price is correct and visible.
updatePriceDisplay(currentVariant, defaultPlanId);
}
});
singlePurchaseButton.addEventListener('click', () => {
sellingPlanContainer.style.display = 'none';
// No need to call updatePriceDisplay here, as the prices should not change.
// The UI state (active button) is handled by Shopyflow.
});
}
if (sellingPlanButtons.length > 0) {
sellingPlanButtons.forEach(button => {
button.addEventListener('click', () => {
const clickedPlanId = button.getAttribute('sf-selling-plan-value');
updatePriceDisplay(currentVariant, clickedPlanId);
});
});
}
Shopyflow.on('optionChange', ({ el, variant }) => {
// Update the globally accessible state variables.
currentVariant = variant;
currentEl = el;
let initialPlanId = null;
try {
const subStateAttr = el.getAttribute('sf-current-subscription-state');
if (subStateAttr) {
const subscriptionState = JSON.parse(subStateAttr);
if (subscriptionState?.[variant.id]) {
initialPlanId = subscriptionState[variant.id].sellingPlanId;
}
}
} catch (e) {
console.error('Error parsing sf-current-subscription-state:', e);
}
// Call the main update function. It will now correctly populate both one-time
// and recurring prices without clearing either.
updatePriceDisplay(currentVariant, initialPlanId);
});
});