diff --git a/Documents/Vibe Coding Projects/dedicatednodes-redesign/dedicatednodes-bare-metal/new-baremetal.html b/Documents/Vibe Coding Projects/dedicatednodes-redesign/dedicatednodes-bare-metal/new-baremetal.html index 265282c..74902bb 100644 --- a/Documents/Vibe Coding Projects/dedicatednodes-redesign/dedicatednodes-bare-metal/new-baremetal.html +++ b/Documents/Vibe Coding Projects/dedicatednodes-redesign/dedicatednodes-bare-metal/new-baremetal.html @@ -4181,153 +4181,30 @@ var wpcf7 = { }); } - // Parse Storage Requirements - More robust parsing - let storageRequirements = []; - if (selectedServer.storage) { - const parts = selectedServer.storage.split('+'); - parts.forEach(part => { - const trimmed = part.trim(); - if (!trimmed) return; + // For instant customization, use the exact preselected IDs from WHMCS URL + options.forEach(opt => { + let selectedValId = preselected[opt.id]; // Use the ID directly from WHMCS - // Match patterns like "2x 1TB NVMe" or "1TB NVMe" - const match = trimmed.match(/^(\d+)x\s+(.+)$/); - if (match) { - storageRequirements.push({ - qty: parseInt(match[1]), - spec: match[2].trim().toLowerCase() + // Validate that the preselected ID exists in the options + if (selectedValId && !opt.values.find(v => v.id === selectedValId)) { + console.warn(`Invalid preselected ID ${selectedValId} for option ${opt.id}`); + selectedValId = null; + } + + // For non-storage options, apply price adjustment + if (opt.type !== 'storage' && selectedValId) { + const selectedVal = opt.values.find(v => v.id === selectedValId); + if (selectedVal && selectedVal.price > 0) { + const offset = selectedVal.price; + opt.values.forEach(v => { + v.price = parseFloat((v.price - offset).toFixed(2)); }); - } else { - storageRequirements.push({ - qty: 1, - spec: trimmed.toLowerCase() - }); - } - }); - } - - // For storage options, we'll handle them in the pooled grid - // So we skip individual storage matching here - const storageOptions = options.filter(opt => opt.type === 'storage'); - const nonStorageOptions = options.filter(opt => opt.type !== 'storage'); - - // Enhanced matching for non-storage instant server specs - nonStorageOptions.forEach(opt => { - let selectedValId = null; - let urlId = preselected[opt.id]; - - // Enhanced RAM matching - if (opt.type === 'ram') { - const ramMatch = selectedServer.ram.match(/(\d+)\s*GB/i); - const ramAmount = ramMatch ? parseInt(ramMatch[1]) : 0; - - // Find the closest RAM option - let bestMatch = null; - let smallestDiff = Infinity; - - opt.values.forEach(val => { - const valMatch = val.text.match(/(\d+)\s*GB/i); - const valAmount = valMatch ? parseInt(valMatch[1]) : 0; - - if (valAmount === ramAmount) { - bestMatch = val.id; - smallestDiff = 0; - } else if (Math.abs(valAmount - ramAmount) < smallestDiff) { - bestMatch = val.id; - smallestDiff = Math.abs(valAmount - ramAmount); - } - }); - - selectedValId = bestMatch; - } - // Enhanced network matching - else if (opt.type === 'network') { - const networkSpec = selectedServer.network.toLowerCase(); - const match = opt.values.find(v => { - const vText = v.text.toLowerCase(); - // Exact match first - if (vText === networkSpec) return true; - // Match bandwidth (e.g., "1gbit", "10gbit") - const vSpeed = vText.match(/(\d+)\s*[gm]bit/); - const sSpeed = networkSpec.match(/(\d+)\s*[gm]bit/); - if (vSpeed && sSpeed && vSpeed[1] === sSpeed[1]) return true; - return false; - }); - selectedValId = match ? match.id : null; - } - // Location matching - else if (opt.type === 'location') { - const match = opt.values.find(v => - v.text.toLowerCase().includes(selectedServer.location.toLowerCase()) || - selectedServer.location.toLowerCase().includes(v.text.toLowerCase()) - ); - selectedValId = match ? match.id : null; - } - // Other component types - else { - // Try to find best match based on text similarity - const serverSpec = selectedServer.cpu ? selectedServer.cpu.toLowerCase() : ''; - if (serverSpec) { - const match = opt.values.find(v => { - const vText = v.text.toLowerCase(); - // Look for key matching terms - const serverTerms = serverSpec.split(/\s+/); - const matchingTerms = serverTerms.filter(term => - term.length > 2 && vText.includes(term) - ); - return matchingTerms.length > 0; - }); - selectedValId = match ? match.id : null; - } - } - - // Fallback to URL ID if available and valid - if (!selectedValId && urlId && opt.values.find(v => v.id === urlId)) { - selectedValId = urlId; - } - - // Final fallback - try stale ID map - if (!selectedValId && urlId && idToTextMap[urlId]) { - const originalText = idToTextMap[urlId]; - const cleanTarget = originalText.replace(/\s?[€$£].*/, '').trim().toLowerCase(); - const match = opt.values.find(v => - v.text.toLowerCase().includes(cleanTarget) || - cleanTarget.includes(v.text.toLowerCase()) - ); - if (match) selectedValId = match.id; - } - - // Last resort - pick first option (avoid "None" if possible) - if (!selectedValId && opt.values.length > 0) { - const nonNoneOptions = opt.values.filter(v => - v.text.trim() !== '-' && - !v.text.toLowerCase().includes('none') && - !v.text.toLowerCase().includes('no hard drive') - ); - selectedValId = nonNoneOptions.length > 0 ? nonNoneOptions[0].id : opt.values[0].id; - } - - // For storage options, we don't adjust prices as they're handled by pooled grid - if (opt.type === 'storage') { - // Don't adjust storage prices - let pooled grid handle it - if (selectedValId) { - preselected[opt.id] = selectedValId; - } - } else { - // For non-storage options, apply price adjustment - if (selectedValId) { - preselected[opt.id] = selectedValId; - const selectedVal = opt.values.find(v => v.id === selectedValId); - if (selectedVal && selectedVal.price > 0) { - const offset = selectedVal.price; - opt.values.forEach(v => { - v.price = parseFloat((v.price - offset).toFixed(2)); - }); - } } } }); - renderDynamicOptions(options, true, preselected, storageRequirements); + // Render options with the exact WHMCS preselected values + renderDynamicOptions(options, true, preselected); } else { renderFallbackOptions(container); @@ -4682,7 +4559,7 @@ var wpcf7 = { updateSummary(); }; - function renderDynamicOptions(options, isInstant = false, preselected = {}, storageRequirements = []) { + function renderDynamicOptions(options, isInstant = false, preselected = {}) { const container = isInstant ? document.getElementById('instantDynamicConfigContainer') : document.getElementById('dynamicConfigContainer'); container.innerHTML = ''; @@ -4784,7 +4661,7 @@ var wpcf7 = { container.appendChild(visualContainer); // Always use pooled grid for these groups as they are by definition identical - container.appendChild(createPooledStorageGrid(groupOpts, isInstant, preselected, groupIndex, storageRequirements)); + container.appendChild(createPooledStorageGrid(groupOpts, isInstant, preselected, groupIndex)); }); } @@ -4811,7 +4688,7 @@ var wpcf7 = { } // New Pooled Storage Grid - function createPooledStorageGrid(storageOptions, isInstant, preselected = {}, groupIndex = 0, storageRequirements = []) { + function createPooledStorageGrid(storageOptions, isInstant, preselected = {}, groupIndex = 0) { const grid = document.createElement('div'); grid.className = 'config-grid'; @@ -4821,14 +4698,12 @@ var wpcf7 = { // Use a unique key for each group to prevent conflicts const poolKey = (isInstant ? 'instant' : 'custom') + '-g' + groupIndex; - // Storage requirements are now passed as a parameter for instant customization - - // Initialize slots with preselected values or defaults + // Initialize slots with preselected values from WHMCS or defaults const initializedSlots = storageOptions.map(opt => { const preId = preselected[opt.id]; - let initialVal = opt.values[0]; // Default + let initialVal = opt.values[0]; // Default (usually None) - // 1. Try ID Match (Standard) + // Use the preselected value from WHMCS if available if (preId) { const found = opt.values.find(v => v.id === preId); if (found) initialVal = found; @@ -4844,102 +4719,31 @@ var wpcf7 = { window.pooledState[poolKey] = { slots: initializedSlots }; - // For instant customization, pre-fill with the server's storage configuration - if (isInstant && storageRequirements.length > 0) { - // Track original drives to avoid double-charging + // For instant customization, track original drives + if (isInstant) { window.originalDrives = []; - // First, reset all slots to None initializedSlots.forEach(slot => { - const noneOption = slot.values.find(v => - v.text.trim() === '-' || - v.text.toLowerCase().includes('none') || - v.text.toLowerCase().includes('no hard drive') - ); - if (noneOption) { - slot.currentVal = noneOption; - configState[slot.label] = noneOption.price; - configIds[slot.id] = noneOption.id; + // Track the preselected drives as "original" + if (preselected[slot.id] && slot.currentVal.text !== '-' && + !slot.currentVal.text.toLowerCase().includes('none')) { + window.originalDrives.push({ + slotId: slot.id, + driveId: slot.currentVal.id, + text: slot.currentVal.text, + price: slot.currentVal.price + }); + + // Original drives have no additional cost (included in base price) + configState[slot.label] = 0; + } else { + configState[slot.label] = slot.currentVal.price; } - }); - // Fill slots based on storage requirements - storageRequirements.forEach(req => { - for (let i = 0; i < req.qty; i++) { - // Find an empty slot - const emptySlot = initializedSlots.find(s => - s.currentVal.text === '-' || - s.currentVal.text.toLowerCase().includes('none') || - s.currentVal.text.toLowerCase().includes('no hard drive') - ); - - if (emptySlot) { - // Find exact matching drive type - const match = emptySlot.values.find(v => { - const vText = v.text.toLowerCase(); - - // Parse both to normalized form for comparison - const normalizeSpec = (spec) => { - return spec.toLowerCase() - .replace(/\s+/g, ' ') - .replace(/nvme/g, '') - .replace(/ssd/g, '') - .replace(/\s+/g, ' ') - .trim(); - }; - - // Extract capacity from value - const valCapacityMatch = vText.match(/(\d+(?:\.\d+)?)(?:\s*tb)?/i); - const valCapacity = valCapacityMatch ? parseFloat(valCapacityMatch[1]) : 0; - - // Extract capacity from requirement - const reqCapacityMatch = req.spec.match(/(\d+(?:\.\d+)?)(?:\s*tb)?/i); - const reqCapacity = reqCapacityMatch ? parseFloat(reqCapacityMatch[1]) : 0; - - // Match capacity exactly - if (Math.abs(valCapacity - reqCapacity) < 0.1) { - // Check for exact brand match first - if (vText.includes('crucial') && req.spec.includes('crucial')) return true; - if (vText.includes('kioxia') && req.spec.includes('kioxia')) return true; - if (vText.includes('samsung') && req.spec.includes('samsung')) return true; - if (vText.includes('solidigm') && req.spec.includes('solidigm')) return true; - if (vText.includes('wd') && req.spec.includes('wd')) return true; - if (vText.includes('seagate') && req.spec.includes('seagate')) return true; - if (vText.includes('kingston') && req.spec.includes('kingston')) return true; - - // If no brand match, check if it's just generic NVMe - if (!req.spec.includes('crucial') && !req.spec.includes('kioxia') && - !req.spec.includes('samsung') && !req.spec.includes('solidigm')) { - return vText.includes('nvme'); - } - } - - return false; - }); - - if (match) { - emptySlot.currentVal = match; - // For instant customization, original drives have no additional cost - configState[emptySlot.label] = 0; // Price is 0 because it's included in base price - configIds[emptySlot.id] = match.id; - - // Track this as an original drive - window.originalDrives.push({ - slotId: emptySlot.id, - driveId: match.id, - text: match.text, - price: match.price - }); - } else { - console.warn(`Could not find matching drive for requirement: ${req.spec}`); - } - } else { - console.warn(`No empty slots available for requirement: ${req.spec}`); - } - } + configIds[slot.id] = slot.currentVal.id; }); } else { - // Pre-set configState for non-instant or fallback + // Pre-set configState for custom servers initializedSlots.forEach(slot => { configState[slot.label] = slot.currentVal.price; configIds[slot.id] = slot.currentVal.id;