UNPKG

invoice-craft

Version:

Customizable, browser-first invoice PDF generator library with modern TypeScript API

848 lines (697 loc) β€’ 23.6 kB
# 🧾 Invoice Craft > **Customizable invoice PDF generator for React, Vue, and vanilla JS - TypeScript, browser-first** [![npm version](https://badge.fury.io/js/invoice-craft.svg)](https://badge.fury.io/js/invoice-craft) [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) A modern, lightweight TypeScript library for generating professional invoice PDFs directly in the browser. Perfect for web applications that need white-label invoice generation without server dependencies. ## ✨ Features ### Core Features - 🎨 **Fully Customizable** - Brand colors, logos, custom fields, and layouts - 🌐 **Framework Agnostic** - Works with React, Vue, Angular, or vanilla JavaScript - πŸ“± **Browser-First** - No server required, generates PDFs client-side - πŸ”§ **TypeScript Ready** - Full type safety and IntelliSense support - 🌍 **Internationalization** - Multi-language support with custom labels - βœ… **Advanced Validation** - Detailed validation with errors and warnings - πŸ“„ **Multiple Templates** - Professional templates ready to use - 🎯 **Lightweight** - Minimal bundle impact with tree-shaking support ### Advanced Features - πŸ” **Live HTML Preview** - Preview invoices before PDF generation - πŸ“€ **Multiple Export Formats** - PDF, HTML, JSON, and CSV export - ⚑ **Batch Processing** - Generate multiple invoices efficiently - πŸ”Œ **Plugin System** - Extensible architecture for custom functionality - 🎨 **Custom Templates** - Build your own templates with the template builder - πŸ“Š **Progress Tracking** - Real-time progress for batch operations - πŸ›‘οΈ **Schema Validation** - Comprehensive data validation with detailed feedback ## πŸš€ Quick Start ### Installation | Package Manager | Command | |----------------|---------| | npm | `npm install invoice-craft` | | yarn | `yarn add invoice-craft` | | pnpm | `pnpm add invoice-craft` | | bun | `bun add invoice-craft` | ### Basic Usage ```typescript import { generateInvoicePdf } from 'invoice-craft'; const invoiceData = { from: { name: "Your Company", address: "123 Business St", email: "contact@company.com" }, to: { name: "Client Name", address: "456 Client Ave", email: "client@email.com" }, invoiceNumber: "INV-001", invoiceDate: "2024-01-15", items: [{ description: "Web Development", quantity: 40, unitPrice: 125.00, taxRate: 0.10 }], currency: "USD" }; const { blob, filename } = await generateInvoicePdf(invoiceData); // Download logic here... ``` ### Module Support | Type | Import Statement | |------|-----------------| | ES Modules | `import { generateInvoicePdf } from 'invoice-craft'` | | CommonJS | `const { generateInvoicePdf } = require('invoice-craft')` | | Dynamic | `const { generateInvoicePdf } = await import('invoice-craft')` | ## 🎨 Customization Options ### Brand Customization | Option | Type | Description | Example | |--------|------|-------------|---------| | `brandColor` | string | Primary brand color | `"#3b82f6"` | | `logoUrl` | string | Company logo URL | `"https://logo.png"` | | `layoutStyle` | string | Template style | `"modern"`, `"minimal"`, `"creative"` | | `filenameTemplate` | function | Custom filename | `({ invoice }) => "inv-${invoice.invoiceNumber}.pdf"` | ### Internationalization Support | Language | Code | Status | |----------|------|--------| | English | `en` | βœ… Default | | Spanish | `es` | βœ… Available | | French | `fr` | βœ… Available | | German | `de` | βœ… Available | | Custom | - | βœ… Configurable | ```typescript const spanishLabels = { invoice: "Factura", invoiceNumber: "NΓΊmero de Factura", date: "Fecha", total: "Total" /* ... more labels */ }; await generateInvoicePdf(invoiceData, { labels: spanishLabels }); ``` ### Validation Options | Function | Purpose | Returns | |----------|---------|---------| | `validateInvoice()` | Basic validation | `ValidationResult` | | `validateInvoiceStrict()` | Strict validation | `ValidationResult` | | Custom validation | Business rules | User-defined | ```typescript import { validateInvoice } from 'invoice-craft'; const result = validateInvoice(invoiceData); if (!result.isValid) console.log(result.errors); ``` ## πŸ–₯️ Framework Integration ### Framework Support | Framework | Status | Features | Example | |-----------|--------|----------|---------| | **React** | βœ… Full Support | Hooks, TypeScript, Components | [View Example](#react-example) | | **Vue 3** | βœ… Full Support | Composition API, TypeScript | [View Example](#vue-example) | | **Angular** | βœ… Compatible | Services, TypeScript | [View Example](#angular-example) | | **Svelte** | βœ… Compatible | Stores, TypeScript | [View Example](#svelte-example) | | **Node.js** | βœ… Server-side | Express, API endpoints | [View Example](#nodejs-example) | ### React Example ```tsx import { generateInvoicePdf, validateInvoice } from 'invoice-craft'; function InvoiceGenerator({ invoiceData }) { const [isGenerating, setIsGenerating] = useState(false); const handleGenerate = async () => { setIsGenerating(true); try { const validation = validateInvoice(invoiceData); if (!validation.isValid) throw new Error('Invalid data'); const { blob, filename } = await generateInvoicePdf(invoiceData); // Download logic... } catch (error) { console.error(error); } finally { setIsGenerating(false); } }; return ( <button onClick={handleGenerate} disabled={isGenerating}> {isGenerating ? 'Generating...' : 'Generate PDF'} </button> ); } ``` ### Vue Example ```vue <template> <div> <button @click="generatePDF" :disabled="isGenerating"> {{ isGenerating ? 'Generating...' : 'Generate PDF' }} </button> <div v-if="validation && !validation.isValid"> <p v-for="error in validation.errors" :key="error.field"> {{ error.field }}: {{ error.message }} </p> </div> </div> </template> <script setup> import { ref } from 'vue'; import { generateInvoicePdf, validateInvoice } from 'invoice-craft'; const isGenerating = ref(false); const validation = ref(null); const generatePDF = async () => { isGenerating.value = true; try { validation.value = validateInvoice(invoiceData); if (!validation.value.isValid) return; const { blob, filename } = await generateInvoicePdf(invoiceData); // Download logic... } finally { isGenerating.value = false; } }; </script> ``` ### Modern Vanilla JavaScript ```javascript import { generateInvoicePdf, generatePreviewHTML, validateInvoice, exportInvoice, generateBatchInvoices, createPlugin, builtInPlugins } from 'invoice-craft'; class InvoiceManager { constructor(containerId) { this.container = document.getElementById(containerId); this.setupEventListeners(); } setupEventListeners() { // Generate PDF this.container.querySelector('#generate-pdf').addEventListener('click', this.handleGeneratePDF.bind(this) ); // Generate Preview this.container.querySelector('#generate-preview').addEventListener('click', this.handleGeneratePreview.bind(this) ); // Export formats this.container.querySelector('#export-html').addEventListener('click', () => this.handleExport('html') ); this.container.querySelector('#export-json').addEventListener('click', () => this.handleExport('json') ); } async handleGeneratePDF() { const button = this.container.querySelector('#generate-pdf'); const originalText = button.textContent; try { button.textContent = 'Generating...'; button.disabled = true; // Validate first const validation = validateInvoice(this.getInvoiceData()); if (!validation.isValid) { this.showErrors(validation.errors); return; } // Generate with plugins const { blob, filename } = await generateInvoicePdf(this.getInvoiceData(), { brandColor: '#3b82f6', layoutStyle: 'modern', plugins: [ builtInPlugins.currencyFormatter, builtInPlugins.dateValidator, this.createCustomPlugin() ] }); this.downloadFile(blob, filename); this.showSuccess(`Generated ${filename}`); } catch (error) { this.showError(error.message); } finally { button.textContent = originalText; button.disabled = false; } } async handleGeneratePreview() { try { const html = generatePreviewHTML(this.getInvoiceData(), { theme: 'light', responsive: true, includeStyles: true }); // Show in modal or iframe this.showPreview(html); } catch (error) { this.showError(error.message); } } async handleExport(format) { try { const result = await exportInvoice(this.getInvoiceData(), { format, includeStyles: format === 'html' }); this.downloadExport(result); } catch (error) { this.showError(error.message); } } createCustomPlugin() { return createPlugin({ name: 'timestamp-plugin', beforeRender: (invoice) => { invoice.notes = `${invoice.notes}\n\nGenerated on ${new Date().toLocaleString()}`; return invoice; } }); } downloadFile(blob, filename) { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } downloadExport(result) { let url; if (result.data instanceof Blob) { url = URL.createObjectURL(result.data); } else { const blob = new Blob([result.data], { type: result.mimeType }); url = URL.createObjectURL(blob); } const a = document.createElement('a'); a.href = url; a.download = result.filename; a.click(); URL.revokeObjectURL(url); } showPreview(html) { const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 1000; display: flex; align-items: center; justify-content: center; `; const iframe = document.createElement('iframe'); iframe.srcdoc = html; iframe.style.cssText = 'width: 90%; height: 90%; border: none; background: white;'; modal.appendChild(iframe); modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); }); document.body.appendChild(modal); } getInvoiceData() { // Get invoice data from form or state return { from: { name: "Your Company", address: "123 Business St\nCity, State 12345", email: "contact@company.com" }, to: { name: "Client Name", address: "456 Client Ave\nClient City, State 67890" }, invoiceNumber: "INV-001", invoiceDate: new Date().toISOString().split('T')[0], items: [ { description: "Service", quantity: 1, unitPrice: 100.00, taxRate: 0.1 } ], currency: "USD" }; } showSuccess(message) { this.showNotification(message, 'success'); } showError(message) { this.showNotification(message, 'error'); } showErrors(errors) { const messages = errors.map(e => `${e.field}: ${e.message}`).join('\n'); this.showNotification(messages, 'error'); } showNotification(message, type) { const notification = document.createElement('div'); notification.textContent = message; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 1001; padding: 12px 20px; border-radius: 6px; color: white; background: ${type === 'success' ? '#10b981' : '#ef4444'}; `; document.body.appendChild(notification); setTimeout(() => notification.remove(), 3000); } } // Initialize document.addEventListener('DOMContentLoaded', () => { new InvoiceManager('invoice-container'); }); // Batch processing example async function processBatchInvoices(invoices) { try { const result = await generateBatchInvoices(invoices, { concurrency: 3, onProgress: (completed, total) => { console.log(`Progress: ${completed}/${total}`); updateProgressBar(completed / total * 100); }, onError: (error, invoice, index) => { console.error(`Failed to process invoice ${index}:`, error); } }); console.log(`Batch completed: ${result.summary.successful} successful, ${result.summary.failed} failed`); // Download all successful PDFs result.success.forEach((item, index) => { setTimeout(() => { const url = URL.createObjectURL(item.blob); const a = document.createElement('a'); a.href = url; a.download = item.filename; a.click(); URL.revokeObjectURL(url); }, index * 500); }); } catch (error) { console.error('Batch processing failed:', error); } } function updateProgressBar(percentage) { const progressBar = document.getElementById('progress-bar'); if (progressBar) { progressBar.style.width = `${percentage}%`; } } ``` ### Node.js Server Usage ```javascript // server.js import express from 'express'; import { generateInvoicePdf, validateInvoice } from 'invoice-craft'; import fs from 'fs/promises'; const app = express(); app.use(express.json()); // Generate invoice endpoint app.post('/api/invoices/generate', async (req, res) => { try { const invoiceData = req.body; // Validate invoice data const validation = validateInvoice(invoiceData); if (!validation.isValid) { return res.status(400).json({ error: 'Invalid invoice data', details: validation.errors }); } // Generate PDF const { blob, filename } = await generateInvoicePdf(invoiceData, { brandColor: '#3b82f6', layoutStyle: 'modern' }); // Convert blob to buffer for Node.js const buffer = Buffer.from(await blob.arrayBuffer()); // Save to file system (optional) await fs.writeFile(`./invoices/${filename}`, buffer); // Send as response res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); res.send(buffer); } catch (error) { console.error('Invoice generation failed:', error); res.status(500).json({ error: 'Internal server error' }); } }); // Batch generation endpoint app.post('/api/invoices/batch', async (req, res) => { try { const { invoices } = req.body; const result = await generateBatchInvoices(invoices, { concurrency: 5, continueOnError: true }); // Save all successful PDFs const savedFiles = []; for (const item of result.success) { const buffer = Buffer.from(await item.blob.arrayBuffer()); await fs.writeFile(`./invoices/${item.filename}`, buffer); savedFiles.push(item.filename); } res.json({ success: true, summary: result.summary, files: savedFiles, errors: result.errors.map(e => ({ index: e.index, message: e.error.message })) }); } catch (error) { console.error('Batch generation failed:', error); res.status(500).json({ error: 'Internal server error' }); } }); app.listen(3000, () => { console.log('Invoice server running on port 3000'); }); ``` ## πŸ“‹ Complete API Reference ### Core Interfaces ```typescript interface InvoiceData { from: { name: string; address: string; email?: string; phone?: string; logoUrl?: string; brandColor?: string; }; to: { name: string; address: string; email?: string; phone?: string; }; invoiceNumber: string; invoiceDate: string; // YYYY-MM-DD format dueDate?: string; // YYYY-MM-DD format items: InvoiceItem[]; currency: string; // ISO currency code (USD, EUR, etc.) terms?: string; notes?: string; } interface InvoiceItem { description: string; quantity: number; unitPrice: number; taxRate?: number; // 0.1 for 10% tax } interface GeneratePdfOptions { brandColor?: string; logoUrl?: string; layoutStyle?: 'default' | 'modern' | 'minimal' | 'creative'; labels?: Partial<Labels>; validate?: (invoice: InvoiceData) => void | ValidationResult; filenameTemplate?: (context: { invoice: InvoiceData }) => string; plugins?: InvoicePlugin[]; customTemplate?: CustomTemplate; exportOptions?: ExportOptions; } ``` ### Export & Validation ```typescript interface ExportOptions { format?: 'pdf' | 'html' | 'json' | 'csv'; compression?: boolean; quality?: 'low' | 'medium' | 'high'; includeStyles?: boolean; brandColor?: string; logoUrl?: string; layoutStyle?: 'default' | 'modern' | 'minimal' | 'creative'; } interface ValidationResult { isValid: boolean; errors: ValidationError[]; warnings: ValidationError[]; } interface ValidationError { field: string; message: string; code: string; severity: 'error' | 'warning'; } ``` ### Batch Processing ```typescript interface BatchOptions { concurrency?: number; onProgress?: (completed: number, total: number) => void; onError?: (error: Error, invoice: InvoiceData, index: number) => void; continueOnError?: boolean; } interface BatchResult { success: Array<{ blob: Blob; filename: string; index: number }>; errors: Array<{ error: Error; invoice: InvoiceData; index: number }>; summary: { total: number; successful: number; failed: number; }; } ``` ### Template System ```typescript interface CustomTemplate { id: string; name: string; description?: string; header: TemplateSection; body: TemplateSection; footer: TemplateSection; styles: TemplateStyles; supportedFeatures?: { logo?: boolean; brandColor?: boolean; rtl?: boolean; extraSections?: boolean; }; } interface TemplateSection { content: string | ((data: any) => string); styles?: TemplateStyles; visible?: boolean | ((data: any) => boolean); } ``` ### Plugin System ```typescript interface InvoicePlugin { name: string; version?: string; beforeRender?: (invoice: InvoiceData) => InvoiceData | Promise<InvoiceData>; afterRender?: (pdf: any) => any | Promise<any>; beforeValidation?: (invoice: InvoiceData) => InvoiceData | Promise<InvoiceData>; afterValidation?: (result: ValidationResult) => ValidationResult | Promise<ValidationResult>; } ``` ## πŸ’‘ Common Usage Patterns ### Integration Examples | Use Case | Key Features | Implementation | |----------|--------------|----------------| | **E-commerce** | Order β†’ Invoice, Auto-validation | Transform order data, validate, generate | | **SaaS Billing** | Batch processing, Multiple formats | Monthly billing, API endpoints | | **Multi-tenant** | Custom branding, Plugins | Tenant-specific templates, dynamic branding | | **Accounting** | Validation, Export formats | Strict validation, CSV/JSON export | | **Freelancing** | Templates, Customization | Personal branding, custom layouts | ### Quick Implementation ```typescript // E-commerce: Order to Invoice const invoiceData = transformOrderToInvoice(order, customer); const { blob } = await generateInvoicePdf(invoiceData, { brandColor: process.env.BRAND_COLOR, layoutStyle: 'modern' }); // SaaS: Batch Billing const result = await generateBatchInvoices(subscriptions, { concurrency: 10, onProgress: (completed, total) => updateProgress(completed, total) }); // Multi-tenant: Custom Branding const tenantPlugin = createPlugin({ name: 'tenant-branding', beforeRender: (invoice) => applyTenantBranding(invoice, tenantId) }); ``` ## οΏ½ Advanced Features ### Feature Overview | Feature | Function | Purpose | Example | |---------|----------|---------|---------| | **HTML Preview** | `generatePreviewHTML()` | Live preview without PDF | `generatePreviewHTML(data, {theme: 'light'})` | | **Export Formats** | `exportInvoice()` | PDF, HTML, JSON, CSV | `exportInvoice(data, {format: 'html'})` | | **Batch Processing** | `generateBatchInvoices()` | Multiple invoices | `generateBatchInvoices(invoices, {concurrency: 5})` | | **Validation** | `validateInvoice()` | Data validation | `validateInvoice(data)` | | **Plugins** | `createPlugin()` | Custom functionality | `createPlugin({name: 'custom'})` | | **Templates** | `createTemplate()` | Custom layouts | `createTemplate('id', 'name')` | ### Quick Examples ```typescript // HTML Preview const html = generatePreviewHTML(invoiceData, {theme: 'light'}); // Export Formats const result = await exportInvoice(invoiceData, {format: 'json'}); // Batch Processing const batch = await generateBatchInvoices(invoices, { concurrency: 3, onProgress: (done, total) => console.log(`${done}/${total}`) }); // Validation const validation = validateInvoice(invoiceData); if (!validation.isValid) console.log(validation.errors); // Custom Plugin const plugin = createPlugin({ name: 'timestamp', beforeRender: (invoice) => { invoice.notes += `\nGenerated: ${new Date().toLocaleString()}`; return invoice; } }); // Custom Template const template = createTemplate('modern', 'Modern Design') .setHeader(data => `<h1>${data.invoice.from.name}</h1>`) .build(); ``` ## 🎯 Advanced Examples ### Custom Filename Generation ```typescript const options = { filenameTemplate: ({ invoice }) => `invoice-${invoice.invoiceNumber}-${invoice.to.name.replace(/\s+/g, '-')}.pdf` }; const { blob, filename } = await generateInvoicePdf(invoiceData, options); // filename: "invoice-INV-001-Client-Name.pdf" ``` ### Error Handling ```typescript try { const { blob, filename } = await generateInvoicePdf(invoiceData); // Success - handle the PDF } catch (error) { if (error.message.includes('validation')) { // Handle validation errors console.error('Invalid invoice data:', error.message); } else { // Handle other errors console.error('PDF generation failed:', error.message); } } ``` ### Batch Export with Progress ```typescript import { exportBatchInvoices } from 'invoice-craft'; const results = await exportBatchInvoices(invoices, { format: 'pdf', concurrency: 5, onProgress: (completed, total) => { const percentage = ((completed / total) * 100).toFixed(1); console.log(`Progress: ${completed}/${total} (${percentage}%)`); } }); // Download all successful exports results.success.forEach(result => { const url = URL.createObjectURL(result.blob); const a = document.createElement('a'); a.href = url; a.download = result.filename; a.click(); URL.revokeObjectURL(url); }); ``` ## πŸ› οΈ Development ### Building from Source ```bash git clone https://github.com/Hamad-Center/invoice-craft.git cd invoice-craft npm install npm run build ``` ### Running Tests ```bash npm test ``` ## πŸ“„ License MIT License - see [LICENSE](LICENSE) file for details. ## 🀝 Contributing Contributions are welcome! Please feel free to submit a Pull Request. ## πŸ“ž Support - πŸ› **Bug Reports**: [GitHub Issues](https://github.com/Hamad-Center/invoice-craft/issues) - πŸ’‘ **Feature Requests**: [GitHub Discussions](https://github.com/Hamad-Center/invoice-craft/discussions) - πŸ“– **Documentation**: [Wiki](https://github.com/Hamad-Center/invoice-craft/wiki) --- Made with ❀️ for developers who need reliable invoice PDF generation.