190 lines
6.4 KiB
JavaScript
190 lines
6.4 KiB
JavaScript
// Constants for pricing
|
|
const HOURLY_RATE = 100; // 100 sats per hour
|
|
const MIN_HOURS = 1;
|
|
const MAX_HOURS = 2160; // 3 months
|
|
const MIN_SATS = HOURLY_RATE * MIN_HOURS;
|
|
const MAX_SATS = 216000; // Maximum for 3 months
|
|
|
|
// Utility functions for duration formatting
|
|
function formatDuration(hours) {
|
|
const exactHours = `${hours} hour${hours === 1 ? '' : 's'}`;
|
|
|
|
// Break down the time into components
|
|
const months = Math.floor(hours / 720);
|
|
const remainingAfterMonths = hours % 720;
|
|
const weeks = Math.floor(remainingAfterMonths / 168);
|
|
const remainingAfterWeeks = remainingAfterMonths % 168;
|
|
const days = Math.floor(remainingAfterWeeks / 24);
|
|
const remainingHours = remainingAfterWeeks % 24;
|
|
|
|
// Build the detailed breakdown
|
|
const parts = [];
|
|
if (months > 0) {
|
|
parts.push(`${months} month${months === 1 ? '' : 's'}`);
|
|
}
|
|
if (weeks > 0) {
|
|
parts.push(`${weeks} week${weeks === 1 ? '' : 's'}`);
|
|
}
|
|
if (days > 0) {
|
|
parts.push(`${days} day${days === 1 ? '' : 's'}`);
|
|
}
|
|
if (remainingHours > 0 || parts.length === 0) {
|
|
parts.push(`${remainingHours} hour${remainingHours === 1 ? '' : 's'}`);
|
|
}
|
|
|
|
// Combine all parts with proper grammar
|
|
let breakdown = '';
|
|
if (parts.length > 1) {
|
|
const lastPart = parts.pop();
|
|
breakdown = parts.join(', ') + ' and ' + lastPart;
|
|
} else {
|
|
breakdown = parts[0];
|
|
}
|
|
|
|
return `${exactHours} (${breakdown})`;
|
|
}
|
|
|
|
// Price calculation with volume discounts
|
|
function calculatePrice(hours) {
|
|
try {
|
|
hours = parseInt(hours);
|
|
if (hours < MIN_HOURS) return MIN_SATS;
|
|
|
|
let basePrice = hours * HOURLY_RATE;
|
|
if (hours >= 2160) { // 3 months
|
|
basePrice = basePrice * 0.75;
|
|
} else if (hours >= 720) { // 30 days
|
|
basePrice = basePrice * 0.85;
|
|
} else if (hours >= 168) { // 7 days
|
|
basePrice = basePrice * 0.90;
|
|
} else if (hours >= 24) { // 1 day
|
|
basePrice = basePrice * 0.95;
|
|
}
|
|
return Math.round(basePrice);
|
|
} catch (error) {
|
|
console.error('Error calculating price:', error);
|
|
return MIN_SATS;
|
|
}
|
|
}
|
|
|
|
// Calculate hours from price
|
|
function calculateHoursFromPrice(sats) {
|
|
try {
|
|
sats = parseInt(sats);
|
|
if (sats < MIN_SATS) return MIN_HOURS;
|
|
if (sats > MAX_SATS) return MAX_HOURS;
|
|
|
|
// Binary search for the closest hour value
|
|
const binarySearchHours = (min, max, targetSats) => {
|
|
while (min <= max) {
|
|
const mid = Math.floor((min + max) / 2);
|
|
const price = calculatePrice(mid);
|
|
|
|
if (price === targetSats) return mid;
|
|
if (price < targetSats) min = mid + 1;
|
|
else max = mid - 1;
|
|
}
|
|
return max;
|
|
};
|
|
|
|
let hours = 0;
|
|
if (sats >= calculatePrice(2160)) {
|
|
hours = Math.floor(sats / (HOURLY_RATE * 0.75));
|
|
} else if (sats >= calculatePrice(720)) {
|
|
hours = binarySearchHours(720, 2159, sats);
|
|
} else if (sats >= calculatePrice(168)) {
|
|
hours = binarySearchHours(168, 719, sats);
|
|
} else if (sats >= calculatePrice(24)) {
|
|
hours = binarySearchHours(24, 167, sats);
|
|
} else {
|
|
hours = binarySearchHours(1, 23, sats);
|
|
}
|
|
|
|
return Math.max(MIN_HOURS, Math.min(MAX_HOURS, hours));
|
|
} catch (error) {
|
|
console.error('Error calculating hours from price:', error);
|
|
return MIN_HOURS;
|
|
}
|
|
}
|
|
|
|
// Update all displays and inputs
|
|
function updateDisplays(hours, skipSource = null) {
|
|
const elements = {
|
|
priceDisplay: document.getElementById('price-display'),
|
|
durationDisplay: document.getElementById('duration-display'),
|
|
customHours: document.getElementById('custom-hours'),
|
|
customSats: document.getElementById('custom-sats')
|
|
};
|
|
|
|
hours = Math.max(MIN_HOURS, Math.min(MAX_HOURS, hours));
|
|
const price = calculatePrice(hours);
|
|
|
|
// Update displays
|
|
if (elements.priceDisplay && elements.durationDisplay) {
|
|
elements.priceDisplay.textContent = price;
|
|
elements.durationDisplay.textContent = formatDuration(hours);
|
|
}
|
|
|
|
// Update inputs (skip the source of the update)
|
|
if (skipSource !== 'hours' && elements.customHours) {
|
|
elements.customHours.value = hours;
|
|
}
|
|
if (skipSource !== 'sats' && elements.customSats) {
|
|
elements.customSats.value = price;
|
|
}
|
|
}
|
|
|
|
// Main pricing interface
|
|
export const Pricing = {
|
|
init() {
|
|
console.log('Initializing pricing system...');
|
|
const elements = {
|
|
customHours: document.getElementById('custom-hours'),
|
|
customSats: document.getElementById('custom-sats'),
|
|
presetButtons: document.querySelectorAll('.duration-preset')
|
|
};
|
|
|
|
// Initial display
|
|
updateDisplays(24); // Start with 24 hours as default
|
|
|
|
// Event listeners for custom inputs
|
|
elements.customHours?.addEventListener('input', (e) => {
|
|
let hours = parseInt(e.target.value) || MIN_HOURS;
|
|
hours = Math.max(MIN_HOURS, Math.min(MAX_HOURS, hours));
|
|
updateDisplays(hours, 'hours');
|
|
});
|
|
|
|
elements.customSats?.addEventListener('input', (e) => {
|
|
let sats = parseInt(e.target.value) || MIN_SATS;
|
|
sats = Math.max(MIN_SATS, Math.min(MAX_SATS, sats));
|
|
const hours = calculateHoursFromPrice(sats);
|
|
updateDisplays(hours, 'sats');
|
|
});
|
|
|
|
// Add blur events to enforce minimums
|
|
elements.customHours?.addEventListener('blur', (e) => {
|
|
if (!e.target.value || parseInt(e.target.value) < MIN_HOURS) {
|
|
updateDisplays(MIN_HOURS, 'hours');
|
|
}
|
|
});
|
|
|
|
elements.customSats?.addEventListener('blur', (e) => {
|
|
if (!e.target.value || parseInt(e.target.value) < MIN_SATS) {
|
|
updateDisplays(MIN_HOURS, 'sats');
|
|
}
|
|
});
|
|
|
|
// Handle preset buttons
|
|
elements.presetButtons.forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const hours = parseInt(button.getAttribute('data-hours'));
|
|
updateDisplays(hours);
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
// Auto-initialize on script load
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
Pricing.init();
|
|
}); |