-
Notifications
You must be signed in to change notification settings - Fork 514
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
298 additions
and
15 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,285 @@ | ||
--- | ||
title: "Invoice - generating PDFs" | ||
publishedAt: "2024-10-23" | ||
summary: "We are excited to announce the launch of Apps in the Midday. You can now easily connect your favorite tools to streamline your workflow." | ||
image: "/images/update/invoice-pdf/pdf.jpg" | ||
tag: "Updates" | ||
--- | ||
|
||
With our upcoming Invoicing feature, we have explored different ways to generate PDF invoices, everything from running Pupper to using a headless browser on Cloudflare, to paid services and generating PDFs using React. | ||
|
||
<br /> | ||
We noticed from the comunity that this is a common question on how to generate PDF invoices, so we decided to share our solution with you. | ||
<br /> | ||
|
||
|
||
## Invoice in Midday | ||
![PDF Invoices](/images/update/invoice-pdf/invoice.jpg) | ||
<br /> | ||
We are building a new experience for invoices in Midday. You will be able to create and send invoices to your customers, and generate PDFs for each invoice. | ||
|
||
<br /> | ||
|
||
Our interface is highly customizable with a visual editor where you can easily change the layout, add your logo, and customize the text to your liking. | ||
|
||
<br /> | ||
|
||
We use an editor based on Tiptap to support rich text, AI genearation for grammar and improvin text with just one click. | ||
|
||
<br /> | ||
|
||
While the editor saves the content using JSON, we also need a way to make this work with our PDF generation. | ||
|
||
<br/> | ||
|
||
When you have sent an invoice to a customer, they will recive a email with a unique link to the invoice. When they click on the link, it will render the invoice in a web page where you and the | ||
customer can communicate in realtime using our chat interface. | ||
|
||
<br /> | ||
|
||
You will also know if the customer has viewed the invoice and if they have any questions about the invoice. | ||
|
||
<br /> | ||
|
||
## PDF Generation | ||
![PDF Invoices](/images/update/invoice-pdf/pdf-invoice.jpg) | ||
<br /> | ||
|
||
There are many ways to generate PDFs, and we have looked into many different solutions. We have also looked into paid services, but we wanted to make sure to give you full control over the invoices and not rely on another service. | ||
|
||
<br /> | ||
|
||
We went with `react-pdf` to generate the PDFs. This is a great library that allows us to generate PDFs using React. We can easily customize the layout and add our own styles to the documents and it feels just like `react-email` concept where we use react to generate our templates. | ||
|
||
<br /> | ||
|
||
The invoice is then generated and saved to your [Vault](https://midday.ai/vault), so we can match it to incoming transactions and mark it as paid. | ||
|
||
<br /> | ||
|
||
We first create an API endpoint that will generate the PDF and return the PDF as Content Type `application/pdf`. | ||
|
||
```tsx | ||
import { InvoiceTemplate, renderToStream } from "@midday/invoice"; | ||
import { getInvoiceQuery } from "@midday/supabase/queries"; | ||
import { createClient } from "@midday/supabase/server"; | ||
import type { NextRequest } from "next/server"; | ||
|
||
export const preferredRegion = ["fra1", "sfo1", "iad1"]; | ||
export const dynamic = "force-dynamic"; | ||
|
||
export async function GET(req: NextRequest) { | ||
const supabase = createClient(); | ||
const requestUrl = new URL(req.url); | ||
const id = requestUrl.searchParams.get("id"); | ||
const size = requestUrl.searchParams.get("size") as "letter" | "a4"; | ||
const preview = requestUrl.searchParams.get("preview") === "true"; | ||
|
||
if (!id) { | ||
return new Response("No invoice id provided", { status: 400 }); | ||
} | ||
|
||
const { data } = await getInvoiceQuery(supabase, id); | ||
|
||
if (!data) { | ||
return new Response("Invoice not found", { status: 404 }); | ||
} | ||
|
||
const stream = await renderToStream(await InvoiceTemplate({ ...data, size })); | ||
|
||
const blob = await new Response(stream).blob(); | ||
|
||
const headers: Record<string, string> = { | ||
"Content-Type": "application/pdf", | ||
"Cache-Control": "no-store, max-age=0", | ||
}; | ||
|
||
if (!preview) { | ||
headers["Content-Disposition"] = | ||
`attachment; filename="${data.invoice_number}.pdf"`; | ||
} | ||
|
||
return new Response(blob, { headers }); | ||
} | ||
``` | ||
|
||
<br /> | ||
|
||
With this approach we can also add `?preview=true` to the URL to generate the PDF in the browser without downloading it. This is useful for previewing the invoice before generating the PDF. | ||
|
||
## React PDF Invoice Template | ||
|
||
And here is the template for the invoice, we register a custom font, generate a QR code and making sections and formatting the invoice. | ||
|
||
<br /> | ||
|
||
You can find the full code for the invoice template [here](https://go.midday.ai/inv). | ||
|
||
|
||
|
||
```tsx | ||
import { Document, Font, Image, Page, Text, View } from "@react-pdf/renderer"; | ||
import QRCodeUtil from "qrcode"; | ||
import { EditorContent } from "../components/editor-content"; | ||
import { LineItems } from "../components/line-items"; | ||
import { Meta } from "../components/meta"; | ||
import { Note } from "../components/note"; | ||
import { PaymentDetails } from "../components/payment-details"; | ||
import { QRCode } from "../components/qr-code"; | ||
import { Summary } from "../components/summary"; | ||
|
||
const CDN_URL = "https://cdn.midday.ai"; | ||
|
||
Font.register({ | ||
family: "GeistMono", | ||
fonts: [ | ||
{ | ||
src: `${CDN_URL}/fonts/GeistMono/ttf/GeistMono-Regular.ttf`, | ||
fontWeight: 400, | ||
}, | ||
{ | ||
src: `${CDN_URL}/fonts/GeistMono/ttf/GeistMono-Medium.ttf`, | ||
fontWeight: 500, | ||
}, | ||
], | ||
}); | ||
|
||
export async function InvoiceTemplate({ | ||
invoice_number, | ||
issue_date, | ||
due_date, | ||
template, | ||
line_items, | ||
customer_details, | ||
from_details, | ||
payment_details, | ||
note_details, | ||
currency, | ||
vat, | ||
tax, | ||
amount, | ||
size = "letter", | ||
link, | ||
}: Props) { | ||
const qrCode = await QRCodeUtil.toDataURL( | ||
link, | ||
{ | ||
width: 40 * 3, | ||
height: 40 * 3, | ||
margin: 0, | ||
}, | ||
); | ||
|
||
return ( | ||
<Document> | ||
<Page | ||
size={size.toUpperCase() as "LETTER" | "A4"} | ||
style={{ | ||
padding: 20, | ||
backgroundColor: "#fff", | ||
fontFamily: "GeistMono", | ||
color: "#000", | ||
}} | ||
> | ||
<View style={{ marginBottom: 20 }}> | ||
{template?.logo_url && ( | ||
<Image | ||
src={template.logo_url} | ||
style={{ | ||
width: 78, | ||
height: 78, | ||
}} | ||
/> | ||
)} | ||
</View> | ||
|
||
<Meta | ||
invoiceNoLabel={template.invoice_no_label} | ||
issueDateLabel={template.issue_date_label} | ||
dueDateLabel={template.due_date_label} | ||
invoiceNo={invoice_number} | ||
issueDate={issue_date} | ||
dueDate={due_date} | ||
/> | ||
|
||
<View style={{ flexDirection: "row" }}> | ||
<View style={{ flex: 1, marginRight: 10 }}> | ||
<View style={{ marginBottom: 20 }}> | ||
<Text style={{ fontSize: 9, fontWeight: 500 }}> | ||
{template.from_label} | ||
</Text> | ||
<EditorContent content={from_details} /> | ||
</View> | ||
</View> | ||
|
||
<View style={{ flex: 1, marginLeft: 10 }}> | ||
<View style={{ marginBottom: 20 }}> | ||
<Text style={{ fontSize: 9, fontWeight: 500 }}> | ||
{template.customer_label} | ||
</Text> | ||
<EditorContent content={customer_details} /> | ||
</View> | ||
</View> | ||
</View> | ||
|
||
<LineItems | ||
lineItems={line_items} | ||
currency={currency} | ||
descriptionLabel={template.description_label} | ||
quantityLabel={template.quantity_label} | ||
priceLabel={template.price_label} | ||
totalLabel={template.total_label} | ||
/> | ||
|
||
<Summary | ||
amount={amount} | ||
tax={tax} | ||
vat={vat} | ||
currency={currency} | ||
totalLabel={template.total_label} | ||
taxLabel={template.tax_label} | ||
vatLabel={template.vat_label} | ||
/> | ||
|
||
<View | ||
style={{ | ||
flex: 1, | ||
flexDirection: "column", | ||
justifyContent: "flex-end", | ||
}} | ||
> | ||
<View style={{ flexDirection: "row" }}> | ||
<View style={{ flex: 1, marginRight: 10 }}> | ||
<PaymentDetails | ||
content={payment_details} | ||
paymentLabel={template.payment_label} | ||
/> | ||
|
||
<QRCode data={qrCode} /> | ||
</View> | ||
|
||
<View style={{ flex: 1, marginLeft: 10 }}> | ||
<Note content={note_details} noteLabel={template.note_label} /> | ||
</View> | ||
</View> | ||
</View> | ||
</Page> | ||
</Document> | ||
); | ||
} | ||
``` | ||
|
||
<br /> | ||
## What's next? | ||
![PDF Invoices](/images/update/invoice-pdf/web-invoice.jpg) | ||
<br /> | ||
|
||
We will be launching the Invoicing feature soon to our early access users, let us know if you want to be included in the early access program to get all the new features as soon as they are ready. | ||
|
||
<br /> | ||
|
||
We would love to hear what you think about the Invoicing feature, and we would love to hear from you if you have any ideas or feedback for the feature. | ||
|
||
<br /> | ||
|
||
[Sign up for an account](https://app.midday.ai) and start using Midday today. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters