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
-
-
-
-
+ // 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 (