/** * @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); }); });