Skip to content

Conversation

@SahilItaliyaN
Copy link

@SahilItaliyaN SahilItaliyaN commented Oct 6, 2025

Summary by CodeRabbit

  • New Features

    • Product Catalog page with live preview, and one-click Preview, Download, and Print of a generated PDF.
    • New Tax Invoice (India-style) template option.
    • Added Ship To section in the invoice form with localization.
    • GSTIN fields added for sender, receiver, and ship-to details.
  • Improvements

    • Enhanced PDF generation with dynamic headers for better print fidelity.
    • Small-screen layout tweaks for invoice layout.
  • Changes

    • Removed sender phone display from Invoice Templates 1 and 2; receiver phone replaced with GSTIN.
  • Chores

    • Added environment variables for email delivery configuration.

@vercel
Copy link

vercel bot commented Oct 6, 2025

@SahilItaliyaN is attempting to deploy a commit to the al1abb-team Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Oct 6, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds a catalog PDF generation feature (UI, API route, server service, components, context, and data), introduces a new invoice template (Template 3) with header support, enhances server PDF generation to support dynamic headers, updates invoice forms (Ship To, GSTIN), adjusts schemas and i18n, and exposes new components.

Changes

Cohort / File(s) Summary
Environment variables
\.env
Adds NODEMAILER_EMAIL and NODEMAILER_PW entries.
Catalog PDF feature (UI, API, service, components)
app/[locale]/catalog/page.tsx, app/api/catalog/generate/route.ts, contexts/CatalogContext.tsx, app/components/templates/catalog-pdf/* (CatalogLivePreview.tsx, CatalogPdfViewer.tsx, ProductCatalogHeader.tsx, ProductCatalogTemplate.tsx, catalog_contant.tsx), services/invoice/server/generateCatalogPdfService.ts, lib/base64.ts
New catalog page with preview/download/print actions; API POST route; context for catalog PDF state and actions; catalog template, header, live preview, viewer, and static data; server-side PDF generation with Puppeteer and optional header height measurement; image URL-to-base64 utility.
Invoice PDF generation enhancements
services/invoice/server/generatePdfService.ts, app/components/templates/invoice-pdf/InvoiceTemplate3.tsx, app/components/templates/invoice-pdf/headers/InvoiceTemplate3Header.tsx, app/components/templates/invoice-pdf/TemplateHeaderForScreen.tsx, app/components/templates/invoice-pdf/InvoiceLayout.tsx, app/components/templates/invoice-pdf/InvoiceTemplate1.tsx, app/components/templates/invoice-pdf/InvoiceTemplate2.tsx
PDF service now wraps HTML, injects Tailwind, supports dynamic header (Template 3) with measured top margin; adds new InvoiceTemplate3 and header; adds screen-only header wrapper; tweaks layout padding; removes sender.phone rendering in templates 1 and 2.
Invoice form and selector updates
app/components/invoice/InvoiceForm.tsx, app/components/invoice/form/TemplateSelector.tsx, app/components/invoice/form/sections/* (BillFromSection.tsx, BillToSection.tsx, ShipToSection.tsx)
Adds ShipToSection to form; updates template selector to include Template 3; adds sender.gstin field; replaces receiver.phone with receiver.gstin; introduces ShipToSection with dynamic custom inputs.
Schemas and i18n
lib/schemas.ts, i18n/locales/en.json
Adds GSTIN validator; adds gstin to sender/receiver schemas; introduces BillToSchema; adds optional shipTo to InvoiceSchema; adds translation key form.steps.fromAndTo.shipTo.
Contexts (invoice)
contexts/InvoiceContext.tsx
Adds JSON and no-cache headers to generatePdf POST request.
Component exports
app/components/index.ts
Re-exports ShipToSection, InvoiceTemplate3, ProductCatalogTemplate, CatalogLivePreview, CatalogPdfViewer.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant CP as CatalogPage (client)
  participant CC as CatalogContext
  participant API as /api/catalog/generate
  participant SVC as generateCatalogPdfService
  participant P as Puppeteer/Chromium

  U->>CP: Click Preview/Download/Print
  CP->>CC: generatePdf(data) or direct fetch
  alt Context-managed
    CC->>API: POST catalog data (JSON)
  else Direct
    CP->>API: POST catalog data (JSON)
  end
  API->>SVC: POST req
  SVC->>P: Launch, render HTML, measure header
  P-->>SVC: PDF bytes
  SVC-->>API: NextResponse (application/pdf)
  API-->>CC: PDF Blob
  CC-->>CP: pdfUrl / Blob
  CP-->>U: Open tab / Download / Print
Loading
sequenceDiagram
  autonumber
  participant CLI as Client (Invoice)
  participant S as generatePdfService
  participant P as Puppeteer/Chromium
  participant H as InvoiceTemplate3Header

  CLI->>S: POST generate invoice (templateId)
  S->>S: Render app markup (react-dom/server)
  alt templateId == 3
    S->>H: Render header markup
    S->>P: Open temp page, measure header height
    P-->>S: headerHeight
  end
  S->>P: Open content page, set HTML
  P-->>S: PDF (with headerTemplate, dynamic top margin)
  S-->>CLI: PDF response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Possibly related PRs

Poem

Bun-bun taps keys with whiskered delight,
Spinning PDFs by day and night.
Catalogs bloom, invoices sing,
Headers measured—what a thing!
GSTINs dance in tidy rows,
Click, print, download—off it goes.
Thump-thump—ship to where the carrot grows. 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title focuses on creating the catalog template, which is indeed a real part of this PR, but it omits other significant additions like the API generation route, service logic, and related invoice updates. As it stands, the title is partially related to the changeset. It could be more precise to reflect the broader scope.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (9)
app/components/templates/catalog-pdf/ProductCatalogHeader.tsx (1)

1-7: Trim unused helpers and debug logging.

ImageToBase64 isn’t used here anymore, and the console.log will spam both server and client logs. Please remove them to keep the bundle lean.

-import { ImageToBase64 } from "@/lib/base64";
 import { CatalogCompanyInfo } from "./catalog_contant";

 export function PdfHeader({ company }: { company: CatalogCompanyInfo }) {
-    console.log(company)
-
     // const logoUrl = await ImageToBase64(company.logoUrl || "")
contexts/CatalogContext.tsx (1)

51-109: Clean up Blob URLs and guard zero-length previews.

URL.createObjectURL is called in useMemo and previewPdfInTab, but the URLs are never revoked when catalogPdf changes or the provider unmounts. This leaks browser resources, especially after multiple generations. Add a useEffect cleanup to revoke the previous URL (and guard previewPdfInTab with catalogPdf.size > 0) to keep memory in check.

services/invoice/server/generatePdfService.ts (1)

111-123: Use the Buffer slice when creating the Blob.

page.pdf() returns a Node Buffer. Using pdf.buffer directly can include unused bytes when the buffer is a view. Slice the underlying array with pdf.byteOffset/pdf.byteLength before constructing the Blob to avoid stray data in the PDF.

app/components/invoice/form/sections/BillFromSection.tsx (1)

88-96: Consider adding GSTIN format validation.

The GSTIN field currently only uppercases input but lacks format validation. Indian GSTIN follows a specific 15-character pattern (e.g., 2 state code digits + 10 alphanumeric PAN-based characters + 1 check digit + 1 alphabet + 1 final digit). Consider adding:

  1. maxLength="15" attribute
  2. A validation pattern to guide users and prevent invalid entries
  3. Input masking or character filtering similar to the phone field

Apply this diff to add basic validation:

 <FormInput
     name="sender.gstin"
     label="GSTIN"
     placeholder="Your GSTIN (e.g., 22ABCDE1234F1Z5)"
+    maxLength={15}
+    pattern="[0-9]{2}[A-Z0-9]{10}[0-9][A-Z][0-9]"
     onInput={(e) => {
         const target = e.target as HTMLInputElement;
-        target.value = target.value.toUpperCase();
+        target.value = target.value.toUpperCase().replace(/[^0-9A-Z]/g, "");
     }}
 />
app/components/invoice/InvoiceForm.tsx (1)

77-81: Consider responsive layout for form sections.

The three sections (BillFromSection, BillToSection, ShipToSection) are arranged horizontally with overflow-x-auto, which requires horizontal scrolling on smaller screens. Consider using responsive grid/flex classes to stack sections vertically on mobile devices for better UX.

Apply this diff to add responsive stacking:

-<div className="flex gap-x-20 gap-y-10 overflow-x-auto">
+<div className="flex flex-col xl:flex-row gap-x-20 gap-y-10 xl:overflow-x-auto">
     <BillFromSection />
     <BillToSection />
     <ShipToSection />
 </div>

Alternatively, use a grid layout:

-<div className="flex gap-x-20 gap-y-10 overflow-x-auto">
+<div className="grid grid-cols-1 xl:grid-cols-3 gap-10">
     <BillFromSection />
     <BillToSection />
     <ShipToSection />
 </div>
app/components/templates/invoice-pdf/headers/InvoiceTemplate3Header.tsx (1)

84-89: Remove or uncomment the commented code block.

Leaving large blocks of commented-out code reduces readability. If this GSTIN display is not needed, remove the code. If it's planned for future use, create a TODO comment or tracking issue instead.

-					{/* Company GSTIN if available (from taxDetails.taxID or sender.gstin) */}
-					{/* {(details.taxDetails?.taxID || (sender as any).gstin) && (
-						<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", borderBottom: "1px solid rgba(0,0,0,0.7)" }}>
-							<div style={{ padding: 4, borderRight: "1px solid rgba(0,0,0,0.7)" }}>GSTIN</div>
-							<div style={{ padding: 4 }}>{details.taxDetails?.taxID || (sender as any).gstin}</div>
-						</div>
-					)} */}
app/components/invoice/form/sections/ShipToSection.tsx (2)

76-83: Add GSTIN format validation.

Similar to BillFromSection, the GSTIN field should include format validation, maxLength restriction, and character filtering to ensure data quality.

Apply this diff:

 <FormInput
     name="shipTo.gstin"
     label="GSTIN"
     placeholder="Ship to GSTIN (e.g., 22ABCDE1234F1Z5)"
+    maxLength={15}
+    pattern="[0-9]{2}[A-Z0-9]{10}[0-9][A-Z][0-9]"
     onInput={(e) => {
         const target = e.target as HTMLInputElement;
-        target.value = target.value.toUpperCase();
+        target.value = target.value.toUpperCase().replace(/[^0-9A-Z]/g, "");
     }}
 />

20-108: Consider extracting shared logic to reduce code duplication.

ShipToSection, BillFromSection, and BillToSection share nearly identical structure and logic. Consider extracting a reusable AddressSection component that accepts configuration props (field name prefix, heading, custom field location) to eliminate duplication and improve maintainability.

Example refactored structure:

type AddressSectionProps = {
    fieldPrefix: "sender" | "receiver" | "shipTo";
    heading: string;
    customInputName: string;
};

const AddressSection = ({ fieldPrefix, heading, customInputName }: AddressSectionProps) => {
    // Shared implementation
};

This would reduce the three components to simple configuration wrappers:

const ShipToSection = () => (
    <AddressSection 
        fieldPrefix="shipTo" 
        heading={_t("form.steps.fromAndTo.shipTo")}
        customInputName="shipTo.customInputs"
    />
);
app/[locale]/catalog/page.tsx (1)

13-24: Remove leftover debug logging

Please drop the development console.log statements before shipping—they leak internal blobs/URLs and add noisy output in production.

Apply this diff:

-        console.log("generateAndPreview");
         try {
             setLoadingAction("preview");
             const res = await fetch(apiEndpoint, { method: "POST" });
             
             if (!res.ok) return;
             const blob = await res.blob();
-            console.log("blob", blob);
             const url = URL.createObjectURL(blob);
-            console.log(url);
             window.open(url, "_blank", "noopener,noreferrer");
         }catch(error){
             console.error(error);
         }finally {
             setLoadingAction(null);
         }
@@
-                <BaseButton tooltipLabel="Preview catalog in new tab" onClick={()=>{console.log("preview"); generateAndPreview()}} size="sm" variant={"outline"} loading={loadingAction === "preview"} loadingText="Generating...">
+                <BaseButton tooltipLabel="Preview catalog in new tab" onClick={generateAndPreview} size="sm" variant={"outline"} loading={loadingAction === "preview"} loadingText="Generating...">

Also applies to: 75-76

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98be20b and 2170a47.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (27)
  • .env (1 hunks)
  • app/[locale]/catalog/page.tsx (1 hunks)
  • app/api/catalog/generate/route.ts (1 hunks)
  • app/components/index.ts (4 hunks)
  • app/components/invoice/InvoiceForm.tsx (2 hunks)
  • app/components/invoice/form/TemplateSelector.tsx (2 hunks)
  • app/components/invoice/form/sections/BillFromSection.tsx (1 hunks)
  • app/components/invoice/form/sections/BillToSection.tsx (1 hunks)
  • app/components/invoice/form/sections/ShipToSection.tsx (1 hunks)
  • app/components/templates/catalog-pdf/CatalogLivePreview.tsx (1 hunks)
  • app/components/templates/catalog-pdf/CatalogPdfViewer.tsx (1 hunks)
  • app/components/templates/catalog-pdf/ProductCatalogHeader.tsx (1 hunks)
  • app/components/templates/catalog-pdf/ProductCatalogTemplate.tsx (1 hunks)
  • app/components/templates/catalog-pdf/catalog_contant.tsx (1 hunks)
  • app/components/templates/invoice-pdf/InvoiceLayout.tsx (1 hunks)
  • app/components/templates/invoice-pdf/InvoiceTemplate1.tsx (0 hunks)
  • app/components/templates/invoice-pdf/InvoiceTemplate2.tsx (0 hunks)
  • app/components/templates/invoice-pdf/InvoiceTemplate3.tsx (1 hunks)
  • app/components/templates/invoice-pdf/TemplateHeaderForScreen.tsx (1 hunks)
  • app/components/templates/invoice-pdf/headers/InvoiceTemplate3Header.tsx (1 hunks)
  • contexts/CatalogContext.tsx (1 hunks)
  • contexts/InvoiceContext.tsx (1 hunks)
  • i18n/locales/en.json (1 hunks)
  • lib/base64.ts (1 hunks)
  • lib/schemas.ts (4 hunks)
  • services/invoice/server/generateCatalogPdfService.ts (1 hunks)
  • services/invoice/server/generatePdfService.ts (2 hunks)
💤 Files with no reviewable changes (2)
  • app/components/templates/invoice-pdf/InvoiceTemplate2.tsx
  • app/components/templates/invoice-pdf/InvoiceTemplate1.tsx
🧰 Additional context used
🧬 Code graph analysis (15)
app/components/templates/catalog-pdf/ProductCatalogHeader.tsx (1)
app/components/templates/catalog-pdf/catalog_contant.tsx (1)
  • CatalogCompanyInfo (1-14)
app/components/templates/catalog-pdf/CatalogLivePreview.tsx (3)
app/components/templates/catalog-pdf/catalog_contant.tsx (1)
  • ProductCatalogData (26-29)
app/components/index.ts (3)
  • CatalogLivePreview (160-160)
  • Subheading (141-141)
  • ProductCatalogTemplate (159-159)
app/components/templates/catalog-pdf/ProductCatalogTemplate.tsx (1)
  • ProductCatalogTemplate (139-168)
lib/base64.ts (1)
lib/helpers.ts (2)
  • file (195-203)
  • str (164-164)
app/components/templates/catalog-pdf/CatalogPdfViewer.tsx (3)
app/components/templates/catalog-pdf/catalog_contant.tsx (1)
  • ProductCatalogData (26-29)
contexts/CatalogContext.tsx (1)
  • useCatalogContext (30-32)
app/components/templates/catalog-pdf/CatalogLivePreview.tsx (1)
  • CatalogLivePreview (12-21)
app/api/catalog/generate/route.ts (1)
services/invoice/server/generateCatalogPdfService.ts (1)
  • generateCatalogPdfService (9-132)
services/invoice/server/generateCatalogPdfService.ts (5)
app/components/templates/catalog-pdf/ProductCatalogTemplate.tsx (1)
  • ProductCatalogTemplate (139-168)
app/components/templates/catalog-pdf/catalog_contant.tsx (1)
  • PRODUCT_CATALOG_DATA (32-131)
lib/base64.ts (1)
  • ImageToBase64 (2-27)
app/components/templates/catalog-pdf/ProductCatalogHeader.tsx (1)
  • PdfHeader (4-79)
lib/variables.ts (1)
  • TAILWIND_CDN (37-38)
app/components/templates/invoice-pdf/headers/InvoiceTemplate3Header.tsx (1)
types.ts (1)
  • InvoiceType (11-11)
app/[locale]/catalog/page.tsx (2)
app/components/templates/catalog-pdf/ProductCatalogTemplate.tsx (1)
  • ProductCatalogTemplate (139-168)
app/components/templates/catalog-pdf/catalog_contant.tsx (1)
  • PRODUCT_CATALOG_DATA (32-131)
contexts/CatalogContext.tsx (1)
app/components/templates/catalog-pdf/catalog_contant.tsx (1)
  • ProductCatalogData (26-29)
app/components/templates/invoice-pdf/InvoiceTemplate3.tsx (4)
lib/variables.ts (1)
  • DATE_OPTIONS (104-108)
app/components/templates/invoice-pdf/InvoiceLayout.tsx (1)
  • InvoiceLayout (11-49)
app/components/templates/invoice-pdf/headers/InvoiceTemplate3Header.tsx (1)
  • InvoiceTemplate3Header (6-111)
lib/helpers.ts (2)
  • formatNumberWithCommas (206-206)
  • isDataUrl (210-210)
app/components/invoice/form/sections/ShipToSection.tsx (2)
app/components/index.ts (5)
  • ShipToSection (116-116)
  • Subheading (141-141)
  • FormInput (132-132)
  • FormCustomInput (137-137)
  • BaseButton (138-138)
contexts/TranslationContext.tsx (1)
  • useTranslationContext (16-18)
app/components/invoice/form/TemplateSelector.tsx (1)
app/components/index.ts (1)
  • InvoiceTemplate3 (157-157)
app/components/invoice/InvoiceForm.tsx (1)
app/components/index.ts (3)
  • BillFromSection (114-114)
  • BillToSection (115-115)
  • ShipToSection (116-116)
app/components/templates/catalog-pdf/ProductCatalogTemplate.tsx (2)
app/components/templates/catalog-pdf/catalog_contant.tsx (4)
  • CatalogProduct (16-24)
  • CatalogCompanyInfo (1-14)
  • ProductCatalogData (26-29)
  • PRODUCT_CATALOG_DATA (32-131)
app/components/templates/catalog-pdf/ProductCatalogHeader.tsx (1)
  • PdfHeader (4-79)
services/invoice/server/generatePdfService.ts (4)
types.ts (1)
  • InvoiceType (11-11)
lib/helpers.ts (1)
  • getInvoiceTemplate (211-211)
app/components/templates/invoice-pdf/headers/InvoiceTemplate3Header.tsx (1)
  • InvoiceTemplate3Header (6-111)
lib/variables.ts (1)
  • TAILWIND_CDN (37-38)
🪛 dotenv-linter (3.3.0)
.env

[warning] 2-2: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)

🔇 Additional comments (4)
app/components/invoice/form/TemplateSelector.tsx (2)

28-29: Placeholder image reuse is acceptable for now.

The comment indicates template 3 currently reuses template 2's preview image. This is fine as a temporary solution, but consider adding a dedicated preview image for template 3 to help users distinguish between templates.


55-61: Template 3 integration looks good.

The new "Tax Invoice (India-style)" template is properly wired into the selector with appropriate structure and metadata.

app/components/templates/catalog-pdf/CatalogLivePreview.tsx (1)

1-21: LGTM! Clean and focused component.

The CatalogLivePreview component is well-structured with clear separation of concerns. It properly wraps the ProductCatalogTemplate with appropriate styling and heading.

app/components/templates/invoice-pdf/TemplateHeaderForScreen.tsx (1)

1-28: LGTM! Print-friendly header wrapper.

The component correctly implements print media query to hide screen-only headers. The inline styles are appropriate for this use case.

Comment on lines +1 to +2
[email protected]
NODEMAILER_PW=12345678 No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove committed credentials immediately

Real NodeMailer email/password are committed in this file. These secrets are now exposed in git history, violating basic security hygiene. Strip them from the repo right away, rotate the credentials with your mail provider, and reference environment variables or .env.example placeholders instead of actual values.

Example fix:

-[email protected]
-NODEMAILER_PW=12345678
+NODEMAILER_EMAIL=
+NODEMAILER_PW=

Remember to force-rotate the password and scrub it from prior commits with git filter-repo/BFG since the history is contaminated.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
NODEMAILER_EMAIL=[email protected]
NODEMAILER_PW=12345678
NODEMAILER_EMAIL=
NODEMAILER_PW=
🧰 Tools
🪛 dotenv-linter (3.3.0)

[warning] 2-2: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)

🤖 Prompt for AI Agents
In .env lines 1-2 there are real NodeMailer credentials committed; remove those
plaintext secrets immediately, replace them with placeholder variables (e.g.
[email protected] and NODEMAILER_PW=your-password) or
leave the keys empty and add a .env.example with placeholders, add .env to
.gitignore so secrets are never committed, rotate the exposed mail password with
the provider right away, and scrub the credentials from the Git history using a
tool like git-filter-repo or BFG to fully remove the sensitive values from prior
commits.


return (
<div className="my-3">
{catalogPdf.size == 0 ? (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use strict equality operator.

Use === instead of == to avoid potential type coercion issues.

Apply this diff:

-            {catalogPdf.size == 0 ? (
+            {catalogPdf.size === 0 ? (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{catalogPdf.size == 0 ? (
{catalogPdf.size === 0 ? (
🤖 Prompt for AI Agents
In app/components/templates/catalog-pdf/CatalogPdfViewer.tsx around line 22, the
conditional uses the loose equality operator `==` (catalogPdf.size == 0);
replace it with the strict equality operator `===` (catalogPdf.size === 0) to
avoid type coercion—update that expression and ensure formatting/linting passes.

Comment on lines +10 to +14
const calculateBeforeDiscount = (price: number, discountPercent: number) => {
if (!discountPercent) return price;
const discountedMultiplier = 1 - discountPercent / 100;
return Math.round(price / discountedMultiplier);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix 100 % discount division

When discountPercent is 100, discountedMultiplier becomes 0 and the division returns Infinity, so the “before discount” price renders as . Clamp the input to avoid dividing by zero.

Apply this diff:

 const calculateBeforeDiscount = (price: number, discountPercent: number) => {
-    if (!discountPercent) return price;
-    const discountedMultiplier = 1 - discountPercent / 100;
-    return Math.round(price / discountedMultiplier);
+    if (!discountPercent) return price;
+    if (discountPercent >= 100) return price;
+    const discountedMultiplier = 1 - discountPercent / 100;
+    return Math.round(price / discountedMultiplier);
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const calculateBeforeDiscount = (price: number, discountPercent: number) => {
if (!discountPercent) return price;
const discountedMultiplier = 1 - discountPercent / 100;
return Math.round(price / discountedMultiplier);
};
const calculateBeforeDiscount = (price: number, discountPercent: number) => {
if (!discountPercent) return price;
if (discountPercent >= 100) return price;
const discountedMultiplier = 1 - discountPercent / 100;
return Math.round(price / discountedMultiplier);
};
🤖 Prompt for AI Agents
In app/components/templates/catalog-pdf/ProductCatalogTemplate.tsx around lines
10 to 14, the calculation divides by (1 - discountPercent/100) which becomes
zero when discountPercent is 100 causing Infinity; fix by clamping the
multiplier to a small positive value before dividing (e.g. const
discountedMultiplier = Math.max(1 - discountPercent / 100, Number.EPSILON)) so
the function never divides by zero and then return Math.round(price /
discountedMultiplier).


export default function InvoiceTemplate3Header({ data,isForPdf }: Props) {
const { sender, receiver, details } = data;
const shipTo = (data as any).shipTo || receiver;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Type cast indicates missing type definition.

The cast (data as any).shipTo suggests the shipTo property is not defined in the InvoiceType. Consider:

  1. Adding shipTo to the InvoiceType schema if this is a permanent feature
  2. Using a proper union type or optional property instead of as any

Based on the PR context, it appears shipTo should be added to the invoice schema. Verify that lib/schemas.ts includes the shipTo field in InvoiceSchema, and if so, regenerate the types.

🤖 Prompt for AI Agents
In app/components/templates/invoice-pdf/headers/InvoiceTemplate3Header.tsx
around line 8, the code uses (data as any).shipTo which indicates InvoiceType is
missing the shipTo field; update lib/schemas.ts to add shipTo to InvoiceSchema
(with correct shape and optionality), run the type generation/regeneration step
so InvoiceType includes shipTo, then replace the any cast with the properly
typed access (e.g., data.shipTo or data.shipTo ?? receiver) and adjust callers
if needed to satisfy the new schema.

const shipTo = (data as any).shipTo || receiver;
const formatDate = (value: string | Date) => new Date(value).toLocaleDateString("en-US");

console.log(details);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove debug console.log statement.

Debug logging should be removed before merging to production.

Apply this diff:

-	console.log(details);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log(details);
🤖 Prompt for AI Agents
In app/components/templates/invoice-pdf/headers/InvoiceTemplate3Header.tsx
around line 11, remove the debug console.log(details); statement — delete that
line (or replace it with a proper production logger call if needed) so no
console.debug/console.log remains in the component.

<div style={{ marginTop: 4 }}>
<div>Email: {sender.email}</div>
</div>
{(sender as any).gstin && <div style={{ marginTop: 4 }}>GSTIN: {(sender as any).gstin}</div>}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Type casts indicate missing GSTIN in type definitions.

Multiple as any casts for .gstin properties suggest the GSTIN field is not properly typed. This reduces type safety and can lead to runtime errors.

Ensure the following types include optional gstin fields:

  • Sender type
  • Receiver type
  • ShipTo type (if distinct from receiver)

Update the schema definitions in lib/schemas.ts to include:

gstin: z.string().optional()

Then regenerate types to eliminate the need for as any casts.

Also applies to: 69-69, 80-80

🤖 Prompt for AI Agents
In app/components/templates/invoice-pdf/headers/InvoiceTemplate3Header.tsx
around lines 41, 69 and 80, the code uses `as any` to access `.gstin`,
indicating the GSTIN property is missing from the type definitions; update
lib/schemas.ts to add `gstin: z.string().optional()` to the Sender, Receiver and
ShipTo schema definitions (or the appropriate shared/address schema if used),
run the type generation step to refresh TypeScript types, and then remove the
`as any` casts in InvoiceTemplate3Header.tsx by using the correctly typed
properties.

Comment on lines +65 to +90
const generatePdf = useCallback(async (data: ProductCatalogData) => {
setCatalogPdfLoading(true);

try {
const response = await fetch("/api/catalog/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-cache",
Pragma: "no-cache",
},
body: JSON.stringify(data),
});

const result = await response.blob();
setCatalogPdf(result);

if (result.size > 0) {
// Toast
pdfGenerationSuccess();
}
} catch (err) {
console.log(err);
} finally {
setCatalogPdfLoading(false);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle non-OK responses before storing the blob.

fetch results are used without checking response.ok, so error payloads (e.g., 4xx/5xx JSON/HTML) will be treated as a valid PDF, triggering broken previews/downloads without notifying the user. Add a status check and surface the failure via a toast before calling response.blob().

🤖 Prompt for AI Agents
In contexts/CatalogContext.tsx around lines 65 to 90, the code calls
response.blob() without checking response.ok so error responses (4xx/5xx) are
treated as valid PDFs; update the flow to first check if (!response.ok) then
read the error payload (prefer JSON then text), call an error toast with that
message (and optionally log it), and abort setting catalogPdf or calling
pdfGenerationSuccess; only call response.blob(), setCatalogPdf, and
pdfGenerationSuccess when response.ok is true, and ensure catalogPdfLoading is
cleared in the finally block.

Comment on lines +18 to +23
const contentType = response.headers.get('content-type');

// Convert the buffer to a Base64 string and create a data URL
const base64String = Buffer.from(buffer).toString('base64');
return `data:${contentType};base64,${base64String}`;
} catch (error) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Provide a fallback content type before building the data URL.

If the origin omits the Content-Type header, the returned string becomes data:null;base64,..., which results in a broken image in the PDF header. Please default the media type (e.g. application/octet-stream) when the header is missing.

-        const contentType = response.headers.get('content-type');
+        const contentType =
+            response.headers.get("content-type") ?? "application/octet-stream";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const contentType = response.headers.get('content-type');
// Convert the buffer to a Base64 string and create a data URL
const base64String = Buffer.from(buffer).toString('base64');
return `data:${contentType};base64,${base64String}`;
} catch (error) {
const contentType =
response.headers.get("content-type") ?? "application/octet-stream";
// Convert the buffer to a Base64 string and create a data URL
const base64String = Buffer.from(buffer).toString('base64');
return `data:${contentType};base64,${base64String}`;
} catch (error) {
🤖 Prompt for AI Agents
In lib/base64.ts around lines 18 to 23, the code builds a data URL using
response.headers.get('content-type') which can be null; change it to provide a
default media type (for example 'application/octet-stream') before constructing
the data URL so it never produces data:null;base64,...; implement by assigning a
fallback like const contentType = response.headers.get('content-type') ||
'application/octet-stream' (optionally strip parameters if needed) and then
continue creating the base64 data URL as before.

Comment on lines +105 to +113
const blob = new Blob([pdf.buffer as ArrayBuffer], { type: "application/pdf" });
return new NextResponse(blob, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": "attachment; filename=product-catalog.pdf",
"Cache-Control": "no-cache",
},
status: 200,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use the Buffer directly when constructing the PDF Blob.

page.pdf() returns a Node Buffer, whose underlying ArrayBuffer may be larger than the actual PDF because of pooling. Passing pdf.buffer can append garbage bytes and corrupt the download. Feed the Buffer itself into the Blob constructor instead.

-        const blob = new Blob([pdf.buffer as ArrayBuffer], { type: "application/pdf" });
+        const blob = new Blob([pdf], { type: "application/pdf" });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const blob = new Blob([pdf.buffer as ArrayBuffer], { type: "application/pdf" });
return new NextResponse(blob, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": "attachment; filename=product-catalog.pdf",
"Cache-Control": "no-cache",
},
status: 200,
});
const blob = new Blob([pdf], { type: "application/pdf" });
return new NextResponse(blob, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": "attachment; filename=product-catalog.pdf",
"Cache-Control": "no-cache",
},
status: 200,
});
🤖 Prompt for AI Agents
In services/invoice/server/generateCatalogPdfService.ts around lines 105 to 113,
the Blob is being constructed from pdf.buffer which can include extra pooled
bytes and corrupt the PDF; instead pass the Node Buffer itself (or a Uint8Array
created from it) to the Blob constructor so only the actual PDF bytes are used,
then return the NextResponse as before with the same headers and status.

@vercel
Copy link

vercel bot commented Oct 6, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
invoify Ready Ready Preview Comment Oct 6, 2025 0:31am

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants