diff --git a/apps/docs/src/components/PricingCalculator/index.tsx b/apps/docs/src/components/PricingCalculator/index.tsx index 337dd37a..9cd1d097 100644 --- a/apps/docs/src/components/PricingCalculator/index.tsx +++ b/apps/docs/src/components/PricingCalculator/index.tsx @@ -240,205 +240,264 @@ const exportToPDF = () => { }; }; - // Open print window - const printWindow = window.open('', '', 'width=800,height=600'); - - printWindow.document.write(` - - - - Zerops Cost Estimate - - - -
-

Zerops Cost Estimate

-

Generated on ${new Date().toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })}

-
+ // Create new PDF document + const doc = new jsPDF({ + orientation: 'portrait', + unit: 'mm', + format: 'a4' + }); -
-

Project Configuration

-
- Selected Plan: ${resources.core === 'lightweight' ? 'Lightweight' : 'Serious'} - $${price.core.toFixed(2)} -
-
+ // Document dimensions + const pageWidth = doc.internal.pageSize.getWidth(); + const margin = 8; + const contentWidth = pageWidth - (margin * 2); + + // Set up fonts - using standard fonts for compatibility + doc.setFont("helvetica"); + + // Color settings + const primaryColor = [104, 186, 178]; // #68BAB2 in RGB + const grayBg = [236, 239, 243]; // #ECEFF3 in RGB + const textColor = [51, 51, 51]; // #333333 in RGB + const secondaryTextColor = [85, 85, 85]; // #555555 in RGB + + // Helper function for text + const addText = (text, x, y, options = {}) => { + const defaultOptions = { + align: 'left', + size: 10, + style: 'normal', + color: textColor + }; + const opts = { ...defaultOptions, ...options }; -${services.map(service => { - const resourceCosts = calculateResourceCosts(service); - return ` -
-
${service.name}
-
- CPU - (${service.cpu} ${service.cpuType}, ${service.nodes} nodes) @ $${service.cpuType === 'shared' ? '0.60' : '6.00'}/CPU = $${formatNumber(resourceCosts.cpu)} -
-
- RAM - (${service.ram}GB × ${service.nodes} nodes) @ $3.00/GB = $${formatNumber(resourceCosts.ram)} -
-
- Disk - (${service.disk}GB × ${service.nodes} nodes) @ $0.05/GB = $${formatNumber(resourceCosts.disk)} -
-
- Service Total - $${formatNumber(calculateServiceCost(service))} -
-
- `; -}).join('')} - -
-

Additional Features

- ${resources.ipv4_addr > 0 ? ` -
- Dedicated IPv4 (${resources.ipv4_addr}) - $${formatNumber(resources.ipv4_addr * price.ipv4_addr)} -
- ` : ''} - ${resources.storage > 0 ? ` -
- Object Storage (${resources.storage} GB) - $${formatNumber(resources.storage * price.storage)} -
- ` : ''} - ${resources.backup > 0 ? ` -
- Extra Backup Space (${resources.backup} GB) - $${formatNumber(resources.backup * price.backup)} -
- ` : ''} - ${resources.buildTime > 0 ? ` -
- Extra Build Time (${resources.buildTime} hours) - $${formatNumber(resources.buildTime * price.buildTime)} -
- ` : ''} - ${resources.egress > 0 ? ` -
- Extra Egress (${resources.egress} GB) - $${formatNumber(resources.egress * price.egress)} -
- ` : ''} -
+ doc.setTextColor(...opts.color); + doc.setFontSize(opts.size); -
-
- Total Monthly Cost - $${calculateTotal()} -
-
+ if (opts.style === 'bold') { + doc.setFont("helvetica", "bold"); + } else { + doc.setFont("helvetica", "normal"); + } - + doc.text(text, x, y, { align: opts.align }); + }; - - - - `); + // Helper function for colored rectangles + const addRect = (x, y, width, height, color) => { + doc.setFillColor(...color); + doc.rect(x, y, width, height, 'F'); + }; + + // Helper for drawing lines + const addLine = (x1, y1, x2, y2, color = [221, 221, 221]) => { + doc.setDrawColor(...color); + doc.setLineWidth(0.1); + doc.line(x1, y1, x2, y2); + }; + + // Current position tracker + let y = 20; + + // Header + addRect(0, 0, pageWidth, 28, primaryColor); + addText('Zerops Cost Estimate', margin, 12, { + size: 16, + style: 'bold', + color: [255, 255, 255] + }); + addText(`Generated on ${new Date().toLocaleDateString('en-GB', { + day: '2-digit', + month: '2-digit', + year: 'numeric' + })}`, margin, 20, { + size: 8, + color: [255, 255, 255] + }); + + y = 32; + + // Project Configuration Section + y += 4; + addText('Project Configuration', margin, y, { + size: 12, + style: 'bold' + }); + y += 8; + addText(`Selected Plan: ${resources.core === 'lightweight' ? 'Lightweight' : 'Serious'}`, + margin + 5, y); + addText(`$${price.core.toFixed(2)}`, pageWidth - margin - 5, y, { + align: 'right', + style: 'bold' + }); + + y += 12; + + addText('Services', margin, y, { + size: 12, + style: 'bold' + }); + y += 8; + + // Services Section + services.forEach((service, index) => { + const resourceCosts = calculateResourceCosts(service); + + // Add a page break if necessary + if (y > 250) { + doc.addPage(); + y = 20; + } + + // Service header + addText(service.name, margin, y, { + size: 12, + style: 'bold', + color: primaryColor + }); + + y += 6; + + // CPU + addText('CPU', margin + 5, y, { color: secondaryTextColor, size: 9 }); + addText(`(${service.cpu} ${service.cpuType}, ${service.nodes} nodes) @ $${service.cpuType === 'shared' ? '0.60' : '6.00'}/CPU = $${formatNumber(resourceCosts.cpu)}`, + pageWidth - margin - 5, y, { + align: 'right', + color: secondaryTextColor, + size: 9 + }); + + y += 6; + + // RAM + addText('RAM', margin + 5, y, { color: secondaryTextColor, size: 9 }); + addText(`(${service.ram}GB × ${service.nodes} nodes) @ $3.00/GB = $${formatNumber(resourceCosts.ram)}`, + pageWidth - margin - 5, y, { + align: 'right', + color: secondaryTextColor, + size: 9 + }); + + y += 6; + + // Disk + addText('Disk', margin + 5, y, { color: secondaryTextColor, size: 9 }); + addText(`(${service.disk}GB × ${service.nodes} nodes) @ $0.05/GB = $${formatNumber(resourceCosts.disk)}`, + pageWidth - margin - 5, y, { + align: 'right', + color: secondaryTextColor, + size: 9 + }); + + y += 6; + + addText('Total', margin + 5, y, { + color: secondaryTextColor, + size: 9, + style: 'bold' + }); + addText(`$${formatNumber(calculateServiceCost(service))}`, pageWidth - margin - 5, y, { + align: 'right', + size: 9, + style: 'bold' + }); + + addLine(margin, y + 5, pageWidth - margin - 5, y + 5); + + y += 12; + }); + + y += 4 + + // Additional Features Section + if (resources.ipv4_addr > 0 || resources.storage > 0 || resources.backup > 0 || + resources.buildTime > 0 || resources.egress > 0) { + + // Add a page break if necessary + if (y > 200) { + doc.addPage(); + y = 20; + } + + // Section background + // Section title + addText('Additional Features', margin, y, { + size: 12, + style: 'bold' + }); + + y += 8; + + if (resources.ipv4_addr > 0) { + addText(`Dedicated IPv4 (${resources.ipv4_addr})`, margin + 5, y); + addText(`$${formatNumber(resources.ipv4_addr * price.ipv4_addr)}`, + pageWidth - margin - 5, y, { align: 'right' }); + y += 8; + } + + if (resources.storage > 0) { + addText(`Object Storage (${resources.storage} GB)`, margin + 5, y); + addText(`$${formatNumber(resources.storage * price.storage)}`, + pageWidth - margin - 5, y, { align: 'right' }); + y += 8; + } + + if (resources.backup > 0) { + addText(`Extra Backup Space (${resources.backup} GB)`, margin + 5, y); + addText(`$${formatNumber(resources.backup * price.backup)}`, + pageWidth - margin - 5, y, { align: 'right' }); + y += 8; + } + + if (resources.buildTime > 0) { + addText(`Extra Build Time (${resources.buildTime} hours)`, margin + 5, y); + addText(`$${formatNumber(resources.buildTime * price.buildTime)}`, + pageWidth - margin - 5, y, { align: 'right' }); + y += 8; + } + + if (resources.egress > 0) { + addText(`Extra Egress (${resources.egress} GB)`, margin + 5, y); + addText(`$${formatNumber(resources.egress * price.egress)}`, + pageWidth - margin - 5, y, { align: 'right' }); + y += 8; + } + } + + // Add a page break if necessary for the total + if (y > 220) { + doc.addPage(); + y = 20; + } + + // Total section + addRect(0, y, pageWidth, 20, primaryColor); + addText('Total Monthly Cost', margin, y + 12, { + size: 12, + color: [255, 255, 255] + }); + addText(`$${calculateTotal()}`, pageWidth - margin - 5, y + 12, { + align: 'right', + size: 14, + style: 'bold', + color: [255, 255, 255] + }); + + y += 30; + + // Footer + addText('All prices are in USD and calculated for a 30-day period', margin, y, { + size: 8, + color: [102, 102, 102] + }); + + y += 5; + + addText('Resources are billed by the minute with hourly credit deduction based on actual usage', + margin, y, { size: 8, color: [102, 102, 102] }); - printWindow.document.close(); + // Save PDF + doc.save('zerops-cost-estimate.pdf'); }; return (