diff --git a/apps/web/app/(dashboard)/invoices/new/new-invoice-form.tsx b/apps/web/app/(dashboard)/invoices/new/new-invoice-form.tsx index d20b41d..d131486 100644 --- a/apps/web/app/(dashboard)/invoices/new/new-invoice-form.tsx +++ b/apps/web/app/(dashboard)/invoices/new/new-invoice-form.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useMemo, useRef, useCallback, useEffect } from 'react'; +import { useState, useMemo, useEffect } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { getSavedLineItems, type SavedLineItemData } from '@/lib/saved-items/actions'; import { @@ -336,10 +336,6 @@ export function NewInvoiceForm({ const [previewTab, setPreviewTab] = useState('payment'); const [showPreviewDetails, setShowPreviewDetails] = useState(true); const [templateName, setTemplateName] = useState('oreko'); - const [pdfGenerating, setPdfGenerating] = useState(false); - - // Refs - const pdfRef = useRef(null); // Pre-fill from quote conversion useEffect(() => { @@ -508,33 +504,6 @@ export function NewInvoiceForm({ } }; - // ─── PDF Download Handler ───────────────────────────── - const handleDownloadPdf = useCallback(async () => { - if (!pdfRef.current) return; - setPdfGenerating(true); - try { - const html2canvas = (await import('html2canvas')).default; - const jsPDF = (await import('jspdf')).default; - - const canvas = await html2canvas(pdfRef.current, { - scale: 2, - useCORS: true, - backgroundColor: '#ffffff', - width: 595, - height: 842, - }); - - const imgData = canvas.toDataURL('image/png'); - const pdf = new jsPDF({ orientation: 'portrait', unit: 'pt', format: 'a4' }); - pdf.addImage(imgData, 'PNG', 0, 0, 595, 842); - pdf.save(`Invoice-${invoiceNumber}.pdf`); - } catch (err) { - console.error('PDF generation failed:', err); - toast.error('Failed to generate PDF. Please try again.'); - } finally { - setPdfGenerating(false); - } - }, [invoiceNumber]); return (
@@ -1678,29 +1647,22 @@ export function NewInvoiceForm({ )} - {/* ─── Download Button ─── */} + {/* ─── Download Button (disabled during creation) ─── */}
+

+ Available after saving +

{/* ─── Footer ─── */} @@ -1736,21 +1698,13 @@ export function NewInvoiceForm({ }} className="bg-white shadow-2xl rounded-sm border border-border/40 flex-shrink-0 relative" > - {/* Download floating button */} + {/* Download floating button (disabled during creation) */} {/* A4 Page Content */} @@ -1897,7 +1851,9 @@ export function NewInvoiceForm({ Pay this Invoice @@ -1980,126 +1936,6 @@ export function NewInvoiceForm({
)} - {/* ═══ HIDDEN A4 RENDER DIV (for PDF capture) ═══ */} -
- {/* Top accent bar */} - {tpl.topBorder && ( -
- )} - - {/* Header */} -
-
-

{businessName}

-

hello@company.com

-
-
-

INVOICE

-

#{invoiceNumber}

-

- Date: {dueDate ? format(dueDate, 'MMM dd, yyyy') : 'Not set'} -

-
-
- - {/* Bill To */} -
-

Bill To

-

- {selectedClient?.name || 'Customer Name'} -

- {selectedClient?.company && ( -

{selectedClient.company}

- )} -
- - {/* Items Table */} -
- - - - - - - - - - - {lineItems.length > 0 ? lineItems.map((item) => ( - - - - - - - )) : ( - - - - )} - -
DescriptionQtyRateAmount
-

{item.name || 'Untitled'}

- {item.description &&

{item.description}

} -
{item.quantity}{formatMoney(item.rate, selectedCurrency)}{formatMoney(item.quantity * item.rate, selectedCurrency)}
- No items added -
-
- - {/* Totals */} -
-
-
- Subtotal - {formatMoney(subtotal, selectedCurrency)} -
- {discountAmount > 0 && ( -
- Discount - -{formatMoney(discountAmount, selectedCurrency)} -
- )} - {taxAmount > 0 && ( -
- Tax ({parsedTaxPercent}%) - {formatMoney(taxAmount, selectedCurrency)} -
- )} -
- Total - {formatMoney(total, selectedCurrency)} -
-
- Balance Due - {formatMoney(total, selectedCurrency)} -
-
-
- - {/* Notes */} - {notes && ( -
-

{notes}

-
- )} -
diff --git a/apps/web/app/(dashboard)/quotes/[id]/edit/edit-quote-form.tsx b/apps/web/app/(dashboard)/quotes/[id]/edit/edit-quote-form.tsx index 1d562d9..05f70c0 100644 --- a/apps/web/app/(dashboard)/quotes/[id]/edit/edit-quote-form.tsx +++ b/apps/web/app/(dashboard)/quotes/[id]/edit/edit-quote-form.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect, useMemo, useRef, useCallback } from 'react'; +import { useState, useEffect, useMemo, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { getSavedLineItems, type SavedLineItemData } from '@/lib/saved-items/actions'; import { @@ -379,7 +379,6 @@ export default function EditQuoteForm({ quote }: EditQuoteFormProps) { const [previewTab, setPreviewTab] = useState('quote'); const [showPreviewDetails, setShowPreviewDetails] = useState(true); const [templateName, setTemplateName] = useState('oreko'); - const [pdfGenerating, setPdfGenerating] = useState(false); const [addItemOpen, setAddItemOpen] = useState(false); const [savedItems, setSavedItems] = useState([]); @@ -391,9 +390,6 @@ export default function EditQuoteForm({ quote }: EditQuoteFormProps) { }).catch(() => {}); }, []); - // Refs - const pdfRef = useRef(null); - // Derived State const selectedClient = useMemo( () => clients.find((c) => c.id === selectedClientId) || quote.client, @@ -541,32 +537,9 @@ export default function EditQuoteForm({ quote }: EditQuoteFormProps) { }; // ─── PDF Download Handler ───────────────────────────── - const handleDownloadPdf = useCallback(async () => { - if (!pdfRef.current) return; - setPdfGenerating(true); - try { - const html2canvas = (await import('html2canvas')).default; - const jsPDF = (await import('jspdf')).default; - - const canvas = await html2canvas(pdfRef.current, { - scale: 2, - useCORS: true, - backgroundColor: '#ffffff', - width: 595, - height: 842, - }); - - const imgData = canvas.toDataURL('image/png'); - const pdf = new jsPDF({ orientation: 'portrait', unit: 'pt', format: 'a4' }); - pdf.addImage(imgData, 'PNG', 0, 0, 595, 842); - pdf.save(`Quote-${quoteNumber}.pdf`); - } catch (err) { - console.error('PDF generation failed:', err); - toast.error('Failed to generate PDF. Please try again.'); - } finally { - setPdfGenerating(false); - } - }, [quoteNumber]); + const handleDownloadPdf = useCallback(() => { + window.open(`/api/pdf/quote/${quote.id}`, '_blank'); + }, [quote.id]); return (
@@ -1599,7 +1572,8 @@ export default function EditQuoteForm({ quote }: EditQuoteFormProps) { Accept Quote
@@ -1786,6 +1752,7 @@ export default function EditQuoteForm({ quote }: EditQuoteFormProps) { View Quote
)} - {/* ═══ HIDDEN A4 RENDER DIV (for PDF capture) ═══ */} -
- {tpl.topBorder && ( -
- )} - -
-
-

{businessName}

-

hello@company.com

-
-
-

QUOTE

-

#{quoteNumber}

-

- Date: {issueDate ? format(issueDate, 'MMM dd, yyyy') : 'Not set'} -

- {expirationDate && ( -

- Valid until: {format(expirationDate, 'MMM dd, yyyy')} -

- )} -
-
- -
-

Prepared For

-

- {selectedClient?.name || 'Customer Name'} -

- {selectedClient?.company && ( -

{selectedClient.company}

- )} -
- -
- - - - - - - - - - - {lineItems.length > 0 ? lineItems.map((item) => ( - - - - - - - )) : ( - - - - )} - -
DescriptionQtyRateAmount
-

{item.name || 'Untitled'}

- {item.description &&

{item.description}

} -
{item.quantity}{formatCurrency(item.rate, currency)}{formatCurrency(item.quantity * item.rate, currency)}
- No items added -
-
- -
-
-
- Subtotal - {formatCurrency(subtotal, currency)} -
- {discountAmount > 0 && ( -
- Discount - -{formatCurrency(discountAmount, currency)} -
- )} -
- Total - {formatCurrency(total, currency)} -
-
- Quote Total - {formatCurrency(total, currency)} -
-
-
- - {notes && ( -
-

{notes}

-
- )} -
diff --git a/apps/web/app/(dashboard)/quotes/new/new-quote-form.tsx b/apps/web/app/(dashboard)/quotes/new/new-quote-form.tsx index a2bb73d..9f42b4a 100644 --- a/apps/web/app/(dashboard)/quotes/new/new-quote-form.tsx +++ b/apps/web/app/(dashboard)/quotes/new/new-quote-form.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect, useMemo, useRef, useCallback } from 'react'; +import { useState, useEffect, useMemo } from 'react'; import { useRouter } from 'next/navigation'; import { getSavedLineItems, type SavedLineItemData } from '@/lib/saved-items/actions'; import { @@ -275,7 +275,6 @@ export default function NewQuoteForm({ defaultCurrency = 'USD' }: NewQuoteFormPr const [previewTab, setPreviewTab] = useState('quote'); const [showPreviewDetails, setShowPreviewDetails] = useState(true); const [templateName, setTemplateName] = useState('oreko'); - const [pdfGenerating, setPdfGenerating] = useState(false); const [addItemOpen, setAddItemOpen] = useState(false); const [savedItems, setSavedItems] = useState([]); @@ -288,9 +287,6 @@ export default function NewQuoteForm({ defaultCurrency = 'USD' }: NewQuoteFormPr }).catch(() => {}); }, []); - // Refs - const pdfRef = useRef(null); - // Derived State const selectedClient = useMemo( () => clients.find((c) => c.id === selectedClientId), @@ -423,33 +419,6 @@ export default function NewQuoteForm({ defaultCurrency = 'USD' }: NewQuoteFormPr // Business name loaded from workspace settings above - // ─── PDF Download Handler ───────────────────────────── - const handleDownloadPdf = useCallback(async () => { - if (!pdfRef.current) return; - setPdfGenerating(true); - try { - const html2canvas = (await import('html2canvas')).default; - const jsPDF = (await import('jspdf')).default; - - const canvas = await html2canvas(pdfRef.current, { - scale: 2, - useCORS: true, - backgroundColor: '#ffffff', - width: 595, - height: 842, - }); - - const imgData = canvas.toDataURL('image/png'); - const pdf = new jsPDF({ orientation: 'portrait', unit: 'pt', format: 'a4' }); - pdf.addImage(imgData, 'PNG', 0, 0, 595, 842); - pdf.save(`Quote-${quoteNumber}.pdf`); - } catch (err) { - console.error('PDF generation failed:', err); - toast.error('Failed to generate PDF. Please try again.'); - } finally { - setPdfGenerating(false); - } - }, [quoteNumber, toast]); return (
@@ -1478,12 +1447,18 @@ export default function NewQuoteForm({ defaultCurrency = 'USD' }: NewQuoteFormPr Accept Quote + {/* Download button (disabled during creation) */} +

+ Available after saving +

{/* ─── Footer ─── */} @@ -1517,20 +1492,13 @@ export default function NewQuoteForm({ defaultCurrency = 'USD' }: NewQuoteFormPr }} className="bg-white shadow-2xl rounded-sm border border-border/40 flex-shrink-0 relative" > + {/* Download floating button (disabled during creation) */}
@@ -1666,7 +1634,9 @@ export default function NewQuoteForm({ defaultCurrency = 'USD' }: NewQuoteFormPr View Quote @@ -1739,119 +1709,6 @@ export default function NewQuoteForm({ defaultCurrency = 'USD' }: NewQuoteFormPr
)} - {/* ═══ HIDDEN A4 RENDER DIV (for PDF capture) ═══ */} -
- {tpl.topBorder && ( -
- )} - -
-
-

{businessName}

-

hello@company.com

-
-
-

QUOTE

-

#{quoteNumber}

-

- Date: {issueDate ? format(issueDate, 'MMM dd, yyyy') : 'Not set'} -

- {expirationDate && ( -

- Valid until: {format(expirationDate, 'MMM dd, yyyy')} -

- )} -
-
- -
-

Prepared For

-

- {selectedClient?.name || 'Customer Name'} -

- {selectedClient?.company && ( -

{selectedClient.company}

- )} -
- -
- - - - - - - - - - - {lineItems.length > 0 ? lineItems.map((item) => ( - - - - - - - )) : ( - - - - )} - -
DescriptionQtyRateAmount
-

{item.name || 'Untitled'}

- {item.description &&

{item.description}

} -
{item.quantity}{formatCurrency(item.rate, currency)}{formatCurrency(item.quantity * item.rate, currency)}
- No items added -
-
- -
-
-
- Subtotal - {formatCurrency(subtotal, currency)} -
- {discountAmount > 0 && ( -
- Discount - -{formatCurrency(discountAmount, currency)} -
- )} -
- Total - {formatCurrency(total, currency)} -
-
- Quote Total - {formatCurrency(total, currency)} -
-
-
- - {notes && ( -
-

{notes}

-
- )} -