-
Notifications
You must be signed in to change notification settings - Fork 660
make the catalog template #1011
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
@SahilItaliyaN is attempting to deploy a commit to the al1abb-team Team on Vercel. A member of the Team first needs to authorize it. |
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (1)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including You can disable this status message by setting the WalkthroughAdds 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~70 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
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. Comment |
There was a problem hiding this 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.
ImageToBase64isn’t used here anymore, and theconsole.logwill 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.createObjectURLis called inuseMemoandpreviewPdfInTab, but the URLs are never revoked whencatalogPdfchanges or the provider unmounts. This leaks browser resources, especially after multiple generations. Add auseEffectcleanup to revoke the previous URL (and guardpreviewPdfInTabwithcatalogPdf.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 NodeBuffer. Usingpdf.bufferdirectly can include unused bytes when the buffer is a view. Slice the underlying array withpdf.byteOffset/pdf.byteLengthbefore constructing theBlobto 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:
maxLength="15"attribute- A validation pattern to guide users and prevent invalid entries
- 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
AddressSectioncomponent 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 loggingPlease drop the development
console.logstatements 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
⛔ Files ignored due to path filters (2)
bun.lockis excluded by!**/*.lockpackage-lock.jsonis 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.
| [email protected] | ||
| NODEMAILER_PW=12345678 No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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 ? ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| {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.
| const calculateBeforeDiscount = (price: number, discountPercent: number) => { | ||
| if (!discountPercent) return price; | ||
| const discountedMultiplier = 1 - discountPercent / 100; | ||
| return Math.round(price / discountedMultiplier); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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; |
There was a problem hiding this comment.
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:
- Adding
shipToto theInvoiceTypeschema if this is a permanent feature - 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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>} |
There was a problem hiding this comment.
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.
| 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); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Summary by CodeRabbit
New Features
Improvements
Changes
Chores