{
    "id": "bhaskar/invoice-generator-pdf",
    "kind": "plan",
    "title": "Invoice Generator with PDF Download",
    "summary": "Fill in client and line items, see a live preview, download a clean PDF. Totals in integer cents, saved in the browser, no backend.",
    "author": "Admin",
    "stars": 0,
    "url": "https://ownlyplans.com/bhaskar/invoice-generator-pdf/",
    "stack": [
        "Next.js",
        "React PDF",
        "Tailwind",
        "TypeScript"
    ],
    "tools": [
        "claude-code",
        "cursor",
        "lovable"
    ],
    "difficulty": "beginner",
    "license": "cc-by-4.0",
    "version": "1.0",
    "overview": "Every freelancer needs one and most invoice tools want a subscription. This plan builds a fast, private invoice generator: your data never leaves the browser, totals are calculated correctly (in cents, so no rounding surprises), and the PDF looks professional.\r\n\r\nIt is also a great small project for learning how to keep one source of truth that both the screen and a PDF use.\r\n\r\nBefore you start: Node.js 20+ and Claude Code, Cursor or Lovable. About 2 hours.",
    "steps": [
        {
            "title": "Create the app",
            "body": "Run:\r\n\r\nnpx create-next-app@latest invoice --ts --tailwind --app\r\ncd invoice\r\nnpm i @react-pdf/renderer zod\r\n\r\nSave CLAUDE.md in the project root.",
            "prompt": "Read CLAUDE.md and confirm the rules back to me in a short list. Then replace the home page with a two-column layout: a form on the left, a preview on the right (stacked on mobile). Placeholders only.",
            "done": "The two-column layout shows at localhost:3000 and stacks on a phone-sized window.",
            "files": [
                "CLAUDE.md"
            ]
        },
        {
            "title": "One source of truth for the invoice",
            "body": "Get the data model and the maths right before any UI. Everything else reads from here.",
            "prompt": "Create lib/invoice.ts: an Invoice type and zod schema (number, issue and due dates, currency, from and to blocks with name/address/email, line items with description, quantity and unit price in cents, tax rate in percent, discount in cents, notes). Add calcTotals(invoice) returning subtotal, tax and total in integer cents (round tax half up once, at the end). Add unit tests for: no items, one item, tax 20%, discount larger than subtotal (total never below 0).",
            "done": "All tests pass with node --test or vitest.",
            "files": [
                "CLAUDE.md"
            ]
        },
        {
            "title": "The form",
            "body": "Line items are the heart of it: add, remove, reorder. Prices are typed in normal currency but stored as cents.",
            "prompt": "Build the form with controlled inputs for every field. Line items: add row, remove row, move up/down, quantity accepts decimals like 1.5, price input converts to cents on blur. Show validation messages from the zod schema under each field. Auto-number new invoices as INV-0001, INV-0002 and so on.",
            "done": "You can add three items, change quantities and see validation on an empty client name.",
            "files": []
        },
        {
            "title": "Live preview",
            "body": "The preview should look like the PDF so there are no surprises.",
            "prompt": "Render the invoice preview on the right from the same Invoice state: header with invoice number and dates, from/to blocks, an items table with right-aligned money columns, then subtotal, tax, discount and a bold total, then notes. Format money with Intl.NumberFormat and the chosen currency.",
            "done": "Every change in the form appears in the preview immediately with correct totals.",
            "files": []
        },
        {
            "title": "PDF download",
            "body": "Use React-pdf to build the PDF from the same data and calcTotals(). Load it only on the client to keep the page fast.",
            "prompt": "Create components/InvoicePdf.tsx with @react-pdf/renderer that mirrors the preview layout on A4. Add a Download PDF button that loads the PDF code with a dynamic import (client only) and saves the file as INV-0001.pdf. Use the built-in Helvetica font.",
            "done": "The downloaded PDF matches the preview and the totals are identical.",
            "files": [
                "React-pdf documentation"
            ]
        },
        {
            "title": "Save, reuse and duplicate",
            "body": "Freelancers send the same kind of invoice every month. Make the second one take ten seconds.",
            "prompt": "Save the \"from\" details and the current draft to localStorage automatically. Add a saved-invoices list (number, client, total, date) with Open, Duplicate (new number, today's date) and Delete with a confirm step. Add Export JSON and Import JSON for backups.",
            "done": "Refreshing keeps your draft; Duplicate creates the next invoice number with the same items.",
            "files": []
        },
        {
            "title": "Final checks",
            "body": "Check the maths, the print view and mobile, then deploy it for free on Vercel.",
            "prompt": "Walk me through a final check: totals with 3 items, 7.5% tax and a discount; a very long client address; 25 line items (PDF must break onto a second page with the table header repeated); a 390px wide phone; keyboard-only use of the form. Fix anything that fails.",
            "done": "All checks pass and the app is live on your Vercel URL.",
            "files": []
        }
    ],
    "files": [
        {
            "type": "markdown",
            "name": "CLAUDE.md",
            "note": "Project root. Cursor users: save as AGENTS.md.",
            "file": "claude",
            "url": "https://ownlyplans.com/bhaskar/invoice-generator-pdf/claude/"
        },
        {
            "type": "link",
            "name": "React-pdf documentation",
            "note": "Components and styling for the PDF.",
            "url": "https://react-pdf.org"
        }
    ],
    "install": "",
    "how_to_use": "Call get_step with step 1, do it, check \"done when\", then continue."
}