@formepdf/resend 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # @formepdf/resend
2
+
3
+ Render a PDF and email it in one call. Uses [Forme](https://github.com/formepdf/forme) for PDF generation and [Resend](https://resend.com) for delivery.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @formepdf/resend resend @formepdf/react @formepdf/core
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { sendPdf } from '@formepdf/resend';
15
+
16
+ await sendPdf({
17
+ resendApiKey: process.env.RESEND_API_KEY,
18
+ from: 'Acme Corp <billing@acme.com>',
19
+ to: 'customer@email.com',
20
+ subject: 'Invoice #001',
21
+ template: 'invoice',
22
+ data: {
23
+ invoiceNumber: 'INV-001',
24
+ date: 'February 25, 2026',
25
+ dueDate: 'March 27, 2026',
26
+ company: { name: 'Acme Corp', initials: 'AC', address: '123 Main St', cityStateZip: 'San Francisco, CA 94105', email: 'billing@acme.com' },
27
+ billTo: { name: 'Jane Smith', company: 'Smith Co', address: '456 Oak Ave', cityStateZip: 'Portland, OR 97201', email: 'jane@smith.co' },
28
+ shipTo: { name: 'Jane Smith', address: '456 Oak Ave', cityStateZip: 'Portland, OR 97201' },
29
+ items: [
30
+ { description: 'Consulting', quantity: 10, unitPrice: 150 },
31
+ ],
32
+ taxRate: 0.08,
33
+ paymentTerms: 'Net 30',
34
+ },
35
+ });
36
+ ```
37
+
38
+ One call. PDF rendered, email sent, invoice attached.
39
+
40
+ ## Custom Templates
41
+
42
+ ```typescript
43
+ import { sendPdf } from '@formepdf/resend';
44
+ import { MyTemplate } from './my-template';
45
+
46
+ await sendPdf({
47
+ resendApiKey: process.env.RESEND_API_KEY,
48
+ from: 'billing@acme.com',
49
+ to: 'customer@email.com',
50
+ subject: 'Your document',
51
+ render: () => MyTemplate({ name: 'Jane' }),
52
+ });
53
+ ```
54
+
55
+ ## Add PDF to an Existing Resend Call
56
+
57
+ ```typescript
58
+ import { renderAndAttach } from '@formepdf/resend';
59
+ import { Resend } from 'resend';
60
+
61
+ const resend = new Resend(process.env.RESEND_API_KEY);
62
+ const pdf = await renderAndAttach({ template: 'invoice', data: invoiceData });
63
+
64
+ await resend.emails.send({
65
+ from: 'billing@acme.com',
66
+ to: 'customer@email.com',
67
+ subject: 'Your invoice',
68
+ html: '<p>See attached.</p>',
69
+ attachments: [pdf],
70
+ });
71
+ ```
72
+
73
+ `renderAndAttach` returns `{ filename, content }` which is exactly what Resend's attachment API expects.
74
+
75
+ ## API
76
+
77
+ ### `sendPdf(options)`
78
+
79
+ Render a PDF and email it. Returns `Promise<{ id: string }>` (Resend's email ID).
80
+
81
+ | Option | Type | Required | Description |
82
+ |--------|------|----------|-------------|
83
+ | `resendApiKey` | `string` | Yes | Resend API key |
84
+ | `from` | `string` | Yes | Sender address |
85
+ | `to` | `string \| string[]` | Yes | Recipient(s) |
86
+ | `subject` | `string` | Yes | Email subject |
87
+ | `template` | `string` | One of template/render | Built-in template name |
88
+ | `data` | `object` | With template | Data for the template |
89
+ | `render` | `() => ReactElement` | One of template/render | Custom render function |
90
+ | `filename` | `string` | No | PDF filename (default: `{template}.pdf`) |
91
+ | `html` | `string` | No | Email body HTML |
92
+ | `text` | `string` | No | Email body plain text |
93
+ | `react` | `ReactElement` | No | React Email component for email body |
94
+ | `cc` | `string \| string[]` | No | CC recipients |
95
+ | `bcc` | `string \| string[]` | No | BCC recipients |
96
+ | `replyTo` | `string` | No | Reply-to address |
97
+
98
+ When `html`, `text`, and `react` are all omitted, a default email body is generated based on the template type.
99
+
100
+ ### `renderAndAttach(options)`
101
+
102
+ Render a PDF and return a Resend-compatible attachment object. Does not send.
103
+
104
+ ### `listTemplates()`
105
+
106
+ Returns available built-in templates with descriptions.
107
+
108
+ ## Built-in Templates
109
+
110
+ | Template | Description |
111
+ |----------|-------------|
112
+ | `invoice` | Line items, tax, totals, company/customer info |
113
+ | `receipt` | Payment confirmation, items, total, payment method |
114
+ | `report` | Multi-section document with title, headings, body text |
115
+ | `letter` | Business letter with letterhead, date, recipient, body |
116
+ | `shipping-label` | From/to addresses, 4x6 label format |
117
+
118
+ ## Links
119
+
120
+ - [Forme](https://github.com/formepdf/forme) -- PDF generation with JSX
121
+ - [Resend](https://resend.com) -- Email for developers
122
+ - [Docs](https://docs.formepdf.com)
@@ -0,0 +1,4 @@
1
+ export declare function buildDefaultEmail(template: string | undefined, data: Record<string, any> | undefined): {
2
+ html: string;
3
+ text: string;
4
+ };
@@ -0,0 +1,49 @@
1
+ export function buildDefaultEmail(template, data) {
2
+ const d = data || {};
3
+ if (template === 'invoice') {
4
+ const customerName = d.customer || 'there';
5
+ const invoiceNum = d.invoiceNumber || '';
6
+ const amount = d.total || '';
7
+ const date = d.date || '';
8
+ const company = d.company || '';
9
+ const lines = [
10
+ `Hi ${customerName},`,
11
+ '',
12
+ 'Please find your invoice attached.',
13
+ '',
14
+ ...(invoiceNum ? [`Invoice: #${invoiceNum}`] : []),
15
+ ...(amount ? [`Amount: ${amount}`] : []),
16
+ ...(date ? [`Date: ${date}`] : []),
17
+ '',
18
+ 'Best regards,',
19
+ company,
20
+ ];
21
+ const text = lines.join('\n');
22
+ const html = lines.map(l => l === '' ? '<br>' : `<p>${l}</p>`).join('\n');
23
+ return { html, text };
24
+ }
25
+ if (template === 'receipt') {
26
+ const customerName = d.customer || 'there';
27
+ const amount = d.total || '';
28
+ const date = d.date || '';
29
+ const company = d.company || '';
30
+ const lines = [
31
+ `Hi ${customerName},`,
32
+ '',
33
+ 'Thank you for your payment. Your receipt is attached.',
34
+ '',
35
+ ...(amount ? [`Amount: ${amount}`] : []),
36
+ ...(date ? [`Date: ${date}`] : []),
37
+ '',
38
+ 'Best regards,',
39
+ company,
40
+ ];
41
+ const text = lines.join('\n');
42
+ const html = lines.map(l => l === '' ? '<br>' : `<p>${l}</p>`).join('\n');
43
+ return { html, text };
44
+ }
45
+ return {
46
+ html: '<p>Your document is attached.</p>',
47
+ text: 'Your document is attached.',
48
+ };
49
+ }
@@ -0,0 +1,4 @@
1
+ export { sendPdf } from './send-pdf.js';
2
+ export { renderAndAttach } from './render-attach.js';
3
+ export { listTemplates } from './templates/index.js';
4
+ export type { SendPdfOptions, RenderAttachOptions } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { sendPdf } from './send-pdf.js';
2
+ export { renderAndAttach } from './render-attach.js';
3
+ export { listTemplates } from './templates/index.js';
@@ -0,0 +1,5 @@
1
+ import type { RenderAttachOptions } from './types.js';
2
+ export declare function renderAndAttach(options: RenderAttachOptions): Promise<{
3
+ filename: string;
4
+ content: Buffer<ArrayBuffer>;
5
+ }>;
@@ -0,0 +1,24 @@
1
+ import { renderDocument } from '@formepdf/core';
2
+ import { getTemplate } from './templates/index.js';
3
+ export async function renderAndAttach(options) {
4
+ const { template, data, render, filename } = options;
5
+ let element;
6
+ if (render) {
7
+ element = render();
8
+ }
9
+ else if (template) {
10
+ const templateFn = getTemplate(template);
11
+ if (!templateFn) {
12
+ throw new Error(`Unknown template: "${template}".`);
13
+ }
14
+ element = templateFn(data || {});
15
+ }
16
+ else {
17
+ throw new Error('Either "template" or "render" must be provided.');
18
+ }
19
+ const pdfBytes = await renderDocument(element);
20
+ return {
21
+ filename: filename || `${template || 'document'}.pdf`,
22
+ content: Buffer.from(pdfBytes),
23
+ };
24
+ }
@@ -0,0 +1,4 @@
1
+ import type { SendPdfOptions } from './types.js';
2
+ export declare function sendPdf(options: SendPdfOptions): Promise<{
3
+ id: string;
4
+ }>;
@@ -0,0 +1,62 @@
1
+ import { Resend } from 'resend';
2
+ import { renderDocument } from '@formepdf/core';
3
+ import { getTemplate } from './templates/index.js';
4
+ import { buildDefaultEmail } from './default-email.js';
5
+ export async function sendPdf(options) {
6
+ const { resendApiKey, from, to, subject, template, data, render, filename, html, text, react, cc, bcc, replyTo, tags, headers, } = options;
7
+ let element;
8
+ if (render) {
9
+ element = render();
10
+ }
11
+ else if (template) {
12
+ const templateFn = getTemplate(template);
13
+ if (!templateFn) {
14
+ throw new Error(`Unknown template: "${template}". Use listTemplates() to see available templates.`);
15
+ }
16
+ element = templateFn(data || {});
17
+ }
18
+ else {
19
+ throw new Error('Either "template" or "render" must be provided.');
20
+ }
21
+ const pdfBytes = await renderDocument(element);
22
+ const pdfFilename = filename || `${template || 'document'}.pdf`;
23
+ const attachment = {
24
+ filename: pdfFilename,
25
+ content: Buffer.from(pdfBytes),
26
+ };
27
+ let emailHtml = html;
28
+ let emailText = text;
29
+ if (!html && !text && !react) {
30
+ const defaultEmail = buildDefaultEmail(template, data);
31
+ emailHtml = defaultEmail.html;
32
+ emailText = defaultEmail.text;
33
+ }
34
+ const resend = new Resend(resendApiKey);
35
+ const emailPayload = {
36
+ from,
37
+ to: Array.isArray(to) ? to : [to],
38
+ subject,
39
+ attachments: [attachment],
40
+ };
41
+ if (react)
42
+ emailPayload.react = react;
43
+ if (emailHtml)
44
+ emailPayload.html = emailHtml;
45
+ if (emailText)
46
+ emailPayload.text = emailText;
47
+ if (cc)
48
+ emailPayload.cc = Array.isArray(cc) ? cc : [cc];
49
+ if (bcc)
50
+ emailPayload.bcc = Array.isArray(bcc) ? bcc : [bcc];
51
+ if (replyTo)
52
+ emailPayload.replyTo = replyTo;
53
+ if (tags)
54
+ emailPayload.tags = tags;
55
+ if (headers)
56
+ emailPayload.headers = headers;
57
+ const { data: result, error } = await resend.emails.send(emailPayload);
58
+ if (error) {
59
+ throw new Error(`Resend error: ${error.message}`);
60
+ }
61
+ return { id: result.id };
62
+ }
@@ -0,0 +1,5 @@
1
+ import type { ReactElement } from 'react';
2
+ export declare function getTemplate(name: string): ((data: any) => ReactElement) | null;
3
+ export declare function listTemplates(): {
4
+ name: string;
5
+ }[];
@@ -0,0 +1,18 @@
1
+ import Invoice from './invoice.js';
2
+ import Receipt from './receipt.js';
3
+ import Report from './report.js';
4
+ import ShippingLabel from './shipping-label.js';
5
+ import Letter from './letter.js';
6
+ const templates = {
7
+ invoice: Invoice,
8
+ receipt: Receipt,
9
+ report: Report,
10
+ 'shipping-label': ShippingLabel,
11
+ letter: Letter,
12
+ };
13
+ export function getTemplate(name) {
14
+ return templates[name] || null;
15
+ }
16
+ export function listTemplates() {
17
+ return Object.keys(templates).map(name => ({ name }));
18
+ }
@@ -0,0 +1 @@
1
+ export default function Invoice(data: any): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Document, Page, View, Text, Table, Row, Cell, Fixed } from '@formepdf/react';
3
+ export default function Invoice(data) {
4
+ const items = data.items || [];
5
+ const subtotal = items.reduce((sum, item) => sum + item.quantity * item.unitPrice, 0);
6
+ const tax = subtotal * (data.taxRate || 0);
7
+ const total = subtotal + tax;
8
+ return (_jsx(Document, { title: `Invoice ${data.invoiceNumber}`, author: data.company.name, children: _jsxs(Page, { size: "Letter", margin: 48, children: [_jsx(Fixed, { position: "footer", children: _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 8, borderTopWidth: 1, borderColor: '#e2e8f0' }, children: [_jsx(Text, { style: { fontSize: 8, color: '#94a3b8' }, children: data.company.name }), _jsxs(Text, { style: { fontSize: 8, color: '#94a3b8' }, children: ["Page ", '{{pageNumber}}', " of ", '{{totalPages}}'] })] }) }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 32 }, children: [_jsxs(View, { children: [_jsx(View, { style: { width: 48, height: 48, backgroundColor: '#2563eb', borderRadius: 8, marginBottom: 12, justifyContent: 'center', alignItems: 'center' }, children: _jsx(Text, { style: { fontSize: 18, fontWeight: 700, color: '#ffffff', textAlign: 'center', lineHeight: 1.2 }, children: data.company.initials }) }), _jsx(Text, { style: { fontSize: 16, fontWeight: 700, color: '#1e293b' }, children: data.company.name }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 4 }, children: data.company.address }), _jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: data.company.cityStateZip }), _jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: data.company.email })] }), _jsxs(View, { style: { alignItems: 'flex-end' }, children: [_jsx(Text, { style: { fontSize: 32, fontWeight: 700, color: '#2563eb' }, children: "INVOICE" }), _jsxs(Text, { style: { fontSize: 10, color: '#64748b', marginTop: 8 }, children: ["Invoice No: ", data.invoiceNumber] }), _jsxs(Text, { style: { fontSize: 10, color: '#64748b', marginTop: 2 }, children: ["Date: ", data.date] }), _jsxs(Text, { style: { fontSize: 10, color: '#64748b', marginTop: 2 }, children: ["Due: ", data.dueDate] })] })] }), _jsxs(View, { style: { flexDirection: 'row', gap: 32, marginBottom: 24 }, children: [_jsxs(View, { style: { flexGrow: 1 }, children: [_jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#2563eb', textTransform: 'uppercase', letterSpacing: 1, marginBottom: 8 }, children: "Bill To" }), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#1e293b' }, children: data.billTo.name }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.billTo.company }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.billTo.address }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.billTo.cityStateZip }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.billTo.email })] }), _jsxs(View, { style: { flexGrow: 1 }, children: [_jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#2563eb', textTransform: 'uppercase', letterSpacing: 1, marginBottom: 8 }, children: "Ship To" }), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#1e293b' }, children: data.shipTo.name }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.shipTo.address }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.shipTo.cityStateZip })] })] }), _jsxs(Table, { columns: [
9
+ { width: { fraction: 0.45 } },
10
+ { width: { fraction: 0.15 } },
11
+ { width: { fraction: 0.2 } },
12
+ { width: { fraction: 0.2 } }
13
+ ], children: [_jsxs(Row, { header: true, style: { backgroundColor: '#2563eb' }, children: [_jsx(Cell, { style: { padding: 10 }, children: _jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#ffffff' }, children: "Description" }) }), _jsx(Cell, { style: { padding: 10 }, children: _jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#ffffff', textAlign: 'center' }, children: "Qty" }) }), _jsx(Cell, { style: { padding: 10 }, children: _jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#ffffff', textAlign: 'right' }, children: "Unit Price" }) }), _jsx(Cell, { style: { padding: 10 }, children: _jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#ffffff', textAlign: 'right' }, children: "Amount" }) })] }), items.map((item, i) => (_jsxs(Row, { style: { backgroundColor: i % 2 === 0 ? '#ffffff' : '#f8fafc' }, children: [_jsx(Cell, { style: { padding: 10 }, children: _jsx(Text, { style: { fontSize: 9, color: '#1e293b' }, children: item.description }) }), _jsx(Cell, { style: { padding: 10 }, children: _jsx(Text, { style: { fontSize: 9, color: '#475569', textAlign: 'center' }, children: item.quantity }) }), _jsx(Cell, { style: { padding: 10 }, children: _jsxs(Text, { style: { fontSize: 9, color: '#475569', textAlign: 'right' }, children: ["$", item.unitPrice.toFixed(2)] }) }), _jsx(Cell, { style: { padding: 10 }, children: _jsxs(Text, { style: { fontSize: 9, color: '#1e293b', textAlign: 'right' }, children: ["$", (item.quantity * item.unitPrice).toFixed(2)] }) })] }, i)))] }), _jsx(View, { style: { flexDirection: 'row', justifyContent: 'flex-end', marginTop: 16 }, children: _jsxs(View, { style: { width: 200 }, children: [_jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', padding: 8 }, children: [_jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: "Subtotal" }), _jsxs(Text, { style: { fontSize: 9, color: '#1e293b' }, children: ["$", subtotal.toFixed(2)] })] }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', padding: 8 }, children: [_jsxs(Text, { style: { fontSize: 9, color: '#64748b' }, children: ["Tax (", (data.taxRate * 100).toFixed(0), "%)"] }), _jsxs(Text, { style: { fontSize: 9, color: '#1e293b' }, children: ["$", tax.toFixed(2)] })] }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', padding: 12, backgroundColor: '#2563eb', borderRadius: 4, marginTop: 4 }, children: [_jsx(Text, { style: { fontSize: 11, fontWeight: 700, color: '#ffffff' }, children: "Total Due" }), _jsxs(Text, { style: { fontSize: 11, fontWeight: 700, color: '#ffffff' }, children: ["$", total.toFixed(2)] })] })] }) }), _jsxs(View, { style: { marginTop: 32, padding: 16, backgroundColor: '#f8fafc', borderRadius: 4 }, children: [_jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#1e293b', marginBottom: 8 }, children: "Payment Terms" }), _jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: data.paymentTerms })] }), data.notes && (_jsxs(View, { style: { marginTop: 16 }, children: [_jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#1e293b', marginBottom: 4 }, children: "Notes" }), _jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: data.notes })] }))] }) }));
14
+ }
@@ -0,0 +1 @@
1
+ export default function Letter(data: any): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Document, Page, View, Text } from '@formepdf/react';
3
+ export default function Letter(data) {
4
+ return (_jsx(Document, { title: `Letter to ${data.recipient.name}`, author: data.sender.name, children: _jsxs(Page, { size: "Letter", margin: { top: 72, right: 72, bottom: 72, left: 72 }, children: [_jsxs(View, { style: { marginBottom: 32 }, children: [_jsx(Text, { style: { fontSize: 16, fontWeight: 700, color: '#1e293b' }, children: data.sender.company }), _jsx(View, { style: { borderTopWidth: 2, borderColor: '#2563eb', marginTop: 8, marginBottom: 12 } }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between' }, children: [_jsxs(View, { children: [_jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: data.sender.address }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.sender.cityStateZip })] }), _jsxs(View, { style: { alignItems: 'flex-end' }, children: [data.sender.phone && (_jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: data.sender.phone })), data.sender.email && (_jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.sender.email }))] })] })] }), _jsx(Text, { style: { fontSize: 10, color: '#1e293b', marginBottom: 24 }, children: data.date }), _jsxs(View, { style: { marginBottom: 24 }, children: [_jsx(Text, { style: { fontSize: 10, color: '#1e293b' }, children: data.recipient.name }), data.recipient.title && (_jsx(Text, { style: { fontSize: 10, color: '#1e293b', marginTop: 2 }, children: data.recipient.title })), data.recipient.company && (_jsx(Text, { style: { fontSize: 10, color: '#1e293b', marginTop: 2 }, children: data.recipient.company })), _jsx(Text, { style: { fontSize: 10, color: '#1e293b', marginTop: 2 }, children: data.recipient.address }), _jsx(Text, { style: { fontSize: 10, color: '#1e293b', marginTop: 2 }, children: data.recipient.cityStateZip })] }), _jsx(Text, { style: { fontSize: 10, color: '#1e293b', marginBottom: 16 }, children: data.salutation }), data.body.map((paragraph, i) => (_jsx(Text, { style: { fontSize: 10, color: '#334155', lineHeight: 1.6, marginBottom: 12 }, children: paragraph }, i))), _jsxs(View, { style: { marginTop: 16 }, children: [_jsx(Text, { style: { fontSize: 10, color: '#1e293b' }, children: data.closing }), _jsx(View, { style: { height: 48 } }), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#1e293b' }, children: data.signatureName }), data.signatureTitle && (_jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.signatureTitle }))] })] }) }));
5
+ }
@@ -0,0 +1 @@
1
+ export default function Receipt(data: any): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Document, Page, View, Text } from '@formepdf/react';
3
+ export default function Receipt(data) {
4
+ const items = data.items || [];
5
+ const subtotal = items.reduce((sum, item) => sum + item.price * (item.quantity || 1), 0);
6
+ const tax = subtotal * (data.taxRate || 0);
7
+ const total = subtotal + tax;
8
+ return (_jsx(Document, { title: `Receipt ${data.receiptNumber}`, author: data.store.name, children: _jsxs(Page, { size: "Letter", margin: { top: 72, right: 120, bottom: 72, left: 120 }, children: [_jsxs(View, { style: { alignItems: 'center', marginBottom: 24 }, children: [_jsx(Text, { style: { fontSize: 20, fontWeight: 700, color: '#1e293b' }, children: data.store.name }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 4 }, children: data.store.address }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.store.cityStateZip }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 2 }, children: data.store.phone })] }), _jsx(View, { style: { borderTopWidth: 1, borderColor: '#e2e8f0', marginBottom: 16 } }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 16 }, children: [_jsxs(Text, { style: { fontSize: 9, color: '#64748b' }, children: ["Receipt #", data.receiptNumber] }), _jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: data.date })] }), items.map((item, i) => (_jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 6, paddingBottom: 6 }, children: [_jsxs(View, { style: { flexDirection: 'row', gap: 8, flexGrow: 1 }, children: [_jsx(Text, { style: { fontSize: 9, color: '#1e293b' }, children: item.name }), (item.quantity || 1) > 1 && (_jsxs(Text, { style: { fontSize: 9, color: '#94a3b8' }, children: ["x", item.quantity] }))] }), _jsxs(Text, { style: { fontSize: 9, color: '#1e293b' }, children: ["$", (item.price * (item.quantity || 1)).toFixed(2)] })] }, i))), _jsx(View, { style: { borderTopWidth: 1, borderColor: '#e2e8f0', marginTop: 12, marginBottom: 12 } }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 4, paddingBottom: 4 }, children: [_jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: "Subtotal" }), _jsxs(Text, { style: { fontSize: 9, color: '#1e293b' }, children: ["$", subtotal.toFixed(2)] })] }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 4, paddingBottom: 4 }, children: [_jsxs(Text, { style: { fontSize: 9, color: '#64748b' }, children: ["Tax (", (data.taxRate * 100).toFixed(1), "%)"] }), _jsxs(Text, { style: { fontSize: 9, color: '#1e293b' }, children: ["$", tax.toFixed(2)] })] }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 12, paddingBottom: 12, borderTopWidth: 2, borderColor: '#1e293b', marginTop: 4 }, children: [_jsx(Text, { style: { fontSize: 12, fontWeight: 700, color: '#1e293b' }, children: "Total" }), _jsxs(Text, { style: { fontSize: 12, fontWeight: 700, color: '#1e293b' }, children: ["$", total.toFixed(2)] })] }), _jsxs(View, { style: { marginTop: 16, paddingTop: 12, paddingBottom: 12, borderTopWidth: 1, borderColor: '#e2e8f0' }, children: [_jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between' }, children: [_jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: "Payment Method" }), _jsx(Text, { style: { fontSize: 9, color: '#1e293b' }, children: data.paymentMethod })] }), data.cardLastFour && (_jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 4 }, children: [_jsx(Text, { style: { fontSize: 9, color: '#64748b' }, children: "Card" }), _jsxs(Text, { style: { fontSize: 9, color: '#1e293b' }, children: ["****", data.cardLastFour] })] }))] }), _jsxs(View, { style: { alignItems: 'center', marginTop: 32 }, children: [_jsx(Text, { style: { fontSize: 10, color: '#64748b' }, children: "Thank you for your purchase!" }), _jsx(Text, { style: { fontSize: 8, color: '#94a3b8', marginTop: 8 }, children: data.store.website })] })] }) }));
9
+ }
@@ -0,0 +1 @@
1
+ export default function Report(data: any): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,179 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Document, Page, View, Text, Svg, Table, Row, Cell, Fixed, PageBreak, StyleSheet } from '@formepdf/react';
3
+ const styles = StyleSheet.create({
4
+ sectionTitle: { fontSize: 20, fontWeight: 700, color: '#0f172a', marginBottom: 12 },
5
+ bodyText: { fontSize: 10, color: '#334155', lineHeight: 1.6, marginBottom: 12 },
6
+ introText: { fontSize: 10, color: '#334155', lineHeight: 1.6, marginBottom: 16 },
7
+ headerFooterText: { fontSize: 8, color: '#94a3b8' },
8
+ headerBar: { flexDirection: 'row', justifyContent: 'space-between', paddingBottom: 8, borderBottomWidth: 1, borderColor: '#e2e8f0', marginBottom: 16 },
9
+ footerBar: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 8, borderTopWidth: 1, borderColor: '#e2e8f0' },
10
+ tableHeaderCell: { padding: 8 },
11
+ tableHeaderText: { fontSize: 9, fontWeight: 700, color: '#ffffff', textAlign: 'right' },
12
+ tableCell: { padding: 8 },
13
+ tableCellText: { fontSize: 9, color: '#334155', textAlign: 'right' },
14
+ chartTitle: { fontSize: 10, fontWeight: 700, color: '#334155', marginBottom: 6 },
15
+ recCard: { flexDirection: 'row', gap: 24, marginBottom: 16, padding: 16, backgroundColor: '#f8fafc', borderRadius: 4, borderLeftWidth: 3, borderColor: '#0f172a' },
16
+ recBadge: { width: 24, height: 24, backgroundColor: '#0f172a', borderRadius: 12, justifyContent: 'center', alignItems: 'center' },
17
+ recBadgeText: { fontSize: 10, fontWeight: 700, color: '#ffffff', lineHeight: 1.2 },
18
+ recTitle: { fontSize: 11, fontWeight: 700, color: '#0f172a', marginBottom: 4 },
19
+ recBody: { fontSize: 9, color: '#475569', lineHeight: 1.5 },
20
+ recLabel: { fontSize: 8, fontWeight: 700, color: '#64748b' },
21
+ recValue: { fontSize: 8, color: '#334155' },
22
+ });
23
+ // ── Chart SVG generators ─────────────────────────────────────────────
24
+ const CHART_COLORS = ['#3b82f6', '#0f172a', '#64748b', '#94a3b8', '#cbd5e1'];
25
+ function renderBarChart(tableData) {
26
+ const totals = tableData.map((r) => ({
27
+ label: r.region,
28
+ value: parseFloat(r.q1.replace(/[$,]/g, '')) + parseFloat(r.q2.replace(/[$,]/g, ''))
29
+ + parseFloat(r.q3.replace(/[$,]/g, '')) + parseFloat(r.q4.replace(/[$,]/g, '')),
30
+ }));
31
+ const maxVal = Math.max(...totals.map(t => t.value));
32
+ const w = 230, h = 150;
33
+ const barAreaTop = 8, barAreaBottom = h - 20;
34
+ const barAreaH = barAreaBottom - barAreaTop;
35
+ const barW = Math.min(32, (w - 20) / totals.length - 8);
36
+ const gap = (w - barW * totals.length) / (totals.length + 1);
37
+ let svg = '';
38
+ // Grid lines
39
+ for (let i = 0; i <= 4; i++) {
40
+ const y = barAreaTop + (barAreaH / 4) * i;
41
+ svg += `<line x1="0" y1="${y}" x2="${w}" y2="${y}" stroke="#e2e8f0" stroke-width="0.5"/>`;
42
+ }
43
+ // Bars and labels
44
+ totals.forEach((t, i) => {
45
+ const x = gap + i * (barW + gap);
46
+ const barH = (t.value / maxVal) * barAreaH;
47
+ const y = barAreaBottom - barH;
48
+ svg += `<rect x="${x}" y="${y}" width="${barW}" height="${barH}" fill="${CHART_COLORS[i % CHART_COLORS.length]}" rx="2"/>`;
49
+ // Region label below bar
50
+ const labelX = x + barW / 2;
51
+ svg += `<rect x="${labelX - 2}" y="${barAreaBottom + 4}" width="4" height="4" fill="${CHART_COLORS[i % CHART_COLORS.length]}" rx="1"/>`;
52
+ });
53
+ return svg;
54
+ }
55
+ /** Approximate a circular arc as cubic bezier segments. */
56
+ function arcPath(cx, cy, r, startAngle, endAngle) {
57
+ let path = '';
58
+ const step = Math.PI / 2; // max 90 degrees per segment
59
+ let a1 = startAngle;
60
+ while (a1 < endAngle - 0.001) {
61
+ const a2 = Math.min(a1 + step, endAngle);
62
+ const alpha = a2 - a1;
63
+ const k = (4 / 3) * Math.tan(alpha / 4);
64
+ const x1 = cx + r * Math.cos(a1), y1 = cy + r * Math.sin(a1);
65
+ const x2 = cx + r * Math.cos(a2), y2 = cy + r * Math.sin(a2);
66
+ const cp1x = x1 - k * r * Math.sin(a1), cp1y = y1 + k * r * Math.cos(a1);
67
+ const cp2x = x2 + k * r * Math.sin(a2), cp2y = y2 - k * r * Math.cos(a2);
68
+ if (a1 === startAngle) {
69
+ path += `M ${f(x1)} ${f(y1)} `;
70
+ }
71
+ path += `C ${f(cp1x)} ${f(cp1y)} ${f(cp2x)} ${f(cp2y)} ${f(x2)} ${f(y2)} `;
72
+ a1 = a2;
73
+ }
74
+ return path;
75
+ }
76
+ function f(n) { return n.toFixed(2); }
77
+ function renderDonutChart(tableData) {
78
+ const totals = tableData.map((r) => ({
79
+ label: r.region,
80
+ value: parseFloat(r.q1.replace(/[$,]/g, '')) + parseFloat(r.q2.replace(/[$,]/g, ''))
81
+ + parseFloat(r.q3.replace(/[$,]/g, '')) + parseFloat(r.q4.replace(/[$,]/g, '')),
82
+ }));
83
+ const sum = totals.reduce((s, t) => s + t.value, 0);
84
+ const cx = 55, cy = 75, r = 48, innerR = 28;
85
+ let svg = '';
86
+ let angle = -Math.PI / 2; // start at top
87
+ totals.forEach((t, i) => {
88
+ const sliceAngle = (t.value / sum) * Math.PI * 2;
89
+ if (sliceAngle < 0.01) {
90
+ angle += sliceAngle;
91
+ return;
92
+ }
93
+ const endAngle = angle + sliceAngle;
94
+ const ix2 = cx + innerR * Math.cos(endAngle), iy2 = cy + innerR * Math.sin(endAngle);
95
+ const innerPath = arcPathReverse(cx, cy, innerR, angle, endAngle);
96
+ const fullD = arcPath(cx, cy, r, angle, endAngle) + `L ${f(ix2)} ${f(iy2)} ` + innerPath + 'Z';
97
+ svg += `<path d="${fullD}" fill="${CHART_COLORS[i % CHART_COLORS.length]}"/>`;
98
+ angle = endAngle;
99
+ });
100
+ return svg;
101
+ }
102
+ /** Reverse arc: draws from endAngle back to startAngle using line-to after the first point. */
103
+ function arcPathReverse(cx, cy, r, startAngle, endAngle) {
104
+ const step = Math.PI / 2;
105
+ const segments = [];
106
+ let a = startAngle;
107
+ while (a < endAngle - 0.001) {
108
+ const a2 = Math.min(a + step, endAngle);
109
+ segments.push({ a1: a, a2 });
110
+ a = a2;
111
+ }
112
+ // Reverse and draw each segment backwards
113
+ let path = '';
114
+ for (let i = segments.length - 1; i >= 0; i--) {
115
+ const { a1, a2 } = segments[i];
116
+ const alpha = a2 - a1;
117
+ const k = (4 / 3) * Math.tan(alpha / 4);
118
+ const x1 = cx + r * Math.cos(a1), y1 = cy + r * Math.sin(a1);
119
+ const x2 = cx + r * Math.cos(a2), y2 = cy + r * Math.sin(a2);
120
+ const cp1x = x1 - k * r * Math.sin(a1), cp1y = y1 + k * r * Math.cos(a1);
121
+ const cp2x = x2 + k * r * Math.sin(a2), cp2y = y2 - k * r * Math.cos(a2);
122
+ // Draw from (x2,y2) to (x1,y1) with swapped control points
123
+ path += `C ${f(cp2x)} ${f(cp2y)} ${f(cp1x)} ${f(cp1y)} ${f(x1)} ${f(y1)} `;
124
+ }
125
+ return path;
126
+ }
127
+ function renderLineChart(tableData) {
128
+ const quarters = ['q1', 'q2', 'q3', 'q4'];
129
+ const qTotals = quarters.map(q => tableData.reduce((sum, r) => sum + parseFloat(r[q].replace(/[$,]/g, '')), 0));
130
+ const minVal = Math.min(...qTotals) * 0.9;
131
+ const maxVal = Math.max(...qTotals) * 1.05;
132
+ const w = 484, h = 140;
133
+ const padL = 16, padR = 16, padT = 12, padB = 24;
134
+ const plotW = w - padL - padR, plotH = h - padT - padB;
135
+ let svg = '';
136
+ // Grid lines
137
+ for (let i = 0; i <= 4; i++) {
138
+ const y = padT + (plotH / 4) * i;
139
+ svg += `<line x1="${padL}" y1="${y}" x2="${w - padR}" y2="${y}" stroke="#e2e8f0" stroke-width="0.5"/>`;
140
+ }
141
+ // Plot points
142
+ const points = qTotals.map((v, i) => ({
143
+ x: padL + (plotW / (quarters.length - 1)) * i,
144
+ y: padT + plotH - ((v - minVal) / (maxVal - minVal)) * plotH,
145
+ }));
146
+ // Fill area under the line
147
+ const areaPath = `M ${f(points[0].x)} ${f(padT + plotH)} L ${points.map(p => `${f(p.x)} ${f(p.y)}`).join(' L ')} L ${f(points[points.length - 1].x)} ${f(padT + plotH)} Z`;
148
+ svg += `<path d="${areaPath}" fill="#3b82f6" fill-opacity="0.08"/>`;
149
+ // Line
150
+ const linePath = points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${f(p.x)} ${f(p.y)}`).join(' ');
151
+ svg += `<path d="${linePath}" fill="none" stroke="#3b82f6" stroke-width="2"/>`;
152
+ // Dots
153
+ points.forEach(p => {
154
+ svg += `<circle cx="${f(p.x)}" cy="${f(p.y)}" r="3.5" fill="#ffffff" stroke="#3b82f6" stroke-width="2"/>`;
155
+ });
156
+ // Quarter markers on x-axis
157
+ points.forEach((p) => {
158
+ svg += `<line x1="${f(p.x)}" y1="${f(padT + plotH)}" x2="${f(p.x)}" y2="${f(padT + plotH + 4)}" stroke="#94a3b8" stroke-width="1"/>`;
159
+ });
160
+ return svg;
161
+ }
162
+ // ── Template ─────────────────────────────────────────────────────────
163
+ export default function Report(data) {
164
+ const tableData = data.sections[1].tableData || [];
165
+ return (_jsxs(Document, { title: data.title, author: data.author, children: [_jsx(Page, { size: "Letter", margin: 72, children: _jsxs(View, { style: { flexGrow: 1, justifyContent: 'center' }, children: [_jsxs(View, { style: { backgroundColor: '#0f172a', padding: 32, borderRadius: 4, marginBottom: 32 }, children: [_jsx(Text, { style: { fontSize: 32, fontWeight: 700, color: '#ffffff' }, children: data.title }), _jsx(Text, { style: { fontSize: 14, color: '#94a3b8', marginTop: 12 }, children: data.subtitle })] }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 24 }, children: [_jsxs(View, { children: [_jsx(Text, { style: { fontSize: 10, color: '#64748b' }, children: "Prepared by" }), _jsx(Text, { style: { fontSize: 12, fontWeight: 700, color: '#1e293b', marginTop: 4 }, children: data.author }), _jsx(Text, { style: { fontSize: 10, color: '#64748b', marginTop: 2 }, children: data.department })] }), _jsxs(View, { style: { alignItems: 'flex-end' }, children: [_jsx(Text, { style: { fontSize: 10, color: '#64748b' }, children: "Date" }), _jsx(Text, { style: { fontSize: 12, fontWeight: 700, color: '#1e293b', marginTop: 4 }, children: data.date }), _jsx(Text, { style: { fontSize: 10, color: '#64748b', marginTop: 2 }, children: data.classification })] })] })] }) }), _jsxs(Page, { size: "Letter", margin: 54, children: [_jsx(Fixed, { position: "header", children: _jsxs(View, { style: styles.headerBar, children: [_jsx(Text, { style: styles.headerFooterText, children: data.company }), _jsx(Text, { style: styles.headerFooterText, children: data.title })] }) }), _jsx(Fixed, { position: "footer", children: _jsxs(View, { style: styles.footerBar, children: [_jsx(Text, { style: styles.headerFooterText, children: data.classification }), _jsxs(Text, { style: styles.headerFooterText, children: ["Page ", '{{pageNumber}}', " of ", '{{totalPages}}'] })] }) }), _jsx(Text, { style: styles.sectionTitle, children: "Table of Contents" }), data.sections.map((section, i) => (_jsx(View, { href: `#${section.title}`, style: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 6, borderBottomWidth: 1, borderColor: '#f1f5f9' }, children: _jsxs(Text, { style: { fontSize: 10, color: '#2563eb', textDecoration: 'underline' }, children: [i + 1, ". ", section.title] }) }, i))), _jsx(PageBreak, {}), _jsxs(Text, { bookmark: data.sections[0].title, style: styles.sectionTitle, children: ["1. ", data.sections[0].title] }), (data.sections[0].paragraphs || []).map((p, i) => (_jsx(Text, { style: styles.bodyText, children: p }, i))), data.keyMetrics && (_jsx(View, { style: { flexDirection: 'row', gap: 12, marginTop: 8, marginBottom: 24 }, children: data.keyMetrics.map((metric, i) => (_jsxs(View, { style: { flexGrow: 1, padding: 16, backgroundColor: '#f8fafc', borderRadius: 4, borderWidth: 1, borderColor: '#e2e8f0' }, children: [_jsx(Text, { style: { fontSize: 20, fontWeight: 700, color: '#0f172a' }, children: metric.value }), _jsx(Text, { style: { fontSize: 9, color: '#64748b', marginTop: 4 }, children: metric.label })] }, i))) })), _jsx(PageBreak, {}), _jsxs(Text, { bookmark: data.sections[1].title, style: styles.sectionTitle, children: ["2. ", data.sections[1].title] }), _jsx(Text, { style: styles.introText, children: data.sections[1].intro }), _jsxs(Table, { columns: [
166
+ { width: { fraction: 0.28 } },
167
+ { width: { fraction: 0.18 } },
168
+ { width: { fraction: 0.18 } },
169
+ { width: { fraction: 0.18 } },
170
+ { width: { fraction: 0.18 } }
171
+ ], children: [_jsxs(Row, { header: true, style: { backgroundColor: '#0f172a' }, children: [_jsx(Cell, { style: styles.tableHeaderCell, children: _jsx(Text, { style: { fontSize: 9, fontWeight: 700, color: '#ffffff' }, children: "Region" }) }), _jsx(Cell, { style: styles.tableHeaderCell, children: _jsx(Text, { style: styles.tableHeaderText, children: "Q1" }) }), _jsx(Cell, { style: styles.tableHeaderCell, children: _jsx(Text, { style: styles.tableHeaderText, children: "Q2" }) }), _jsx(Cell, { style: styles.tableHeaderCell, children: _jsx(Text, { style: styles.tableHeaderText, children: "Q3" }) }), _jsx(Cell, { style: styles.tableHeaderCell, children: _jsx(Text, { style: styles.tableHeaderText, children: "Q4" }) })] }), tableData.map((row, i) => (_jsxs(Row, { style: { backgroundColor: i % 2 === 0 ? '#ffffff' : '#f8fafc' }, children: [_jsx(Cell, { style: styles.tableCell, children: _jsx(Text, { style: { fontSize: 9, color: '#334155', fontWeight: 700 }, children: row.region }) }), _jsx(Cell, { style: styles.tableCell, children: _jsx(Text, { style: styles.tableCellText, children: row.q1 }) }), _jsx(Cell, { style: styles.tableCell, children: _jsx(Text, { style: styles.tableCellText, children: row.q2 }) }), _jsx(Cell, { style: styles.tableCell, children: _jsx(Text, { style: styles.tableCellText, children: row.q3 }) }), _jsx(Cell, { style: styles.tableCell, children: _jsx(Text, { style: styles.tableCellText, children: row.q4 }) })] }, i)))] }), _jsx(PageBreak, {}), _jsxs(Text, { bookmark: data.sections[2].title, style: styles.sectionTitle, children: ["3. ", data.sections[2].title] }), _jsx(Text, { style: styles.introText, children: data.sections[2].intro }), _jsxs(View, { style: { flexDirection: 'row', gap: 16, marginBottom: 24 }, children: [_jsxs(View, { style: { flexGrow: 1 }, children: [_jsx(Text, { style: styles.chartTitle, children: "Revenue by Region" }), _jsxs(View, { style: { backgroundColor: '#f8fafc', borderRadius: 4, borderWidth: 1, borderColor: '#e2e8f0', padding: 8 }, children: [_jsx(Svg, { width: 230, height: 150, viewBox: "0 0 230 150", content: renderBarChart(tableData) }), _jsx(View, { style: { gap: 3, marginTop: 8 }, children: tableData.map((row, i) => (_jsxs(View, { style: { flexDirection: 'row', alignItems: 'center', gap: 4 }, children: [_jsx(View, { style: { width: 8, height: 8, borderRadius: 2, backgroundColor: CHART_COLORS[i % CHART_COLORS.length] } }), _jsx(Text, { style: { fontSize: 7, color: '#64748b' }, children: row.region })] }, i))) })] })] }), _jsxs(View, { style: { flexGrow: 1 }, children: [_jsx(Text, { style: styles.chartTitle, children: "Market Share" }), _jsxs(View, { style: { backgroundColor: '#f8fafc', borderRadius: 4, borderWidth: 1, borderColor: '#e2e8f0', padding: 8 }, children: [_jsx(View, { style: { alignItems: 'center' }, children: _jsx(Svg, { width: 110, height: 150, viewBox: "0 0 110 150", content: renderDonutChart(tableData) }) }), _jsx(View, { style: { gap: 3, marginTop: 8 }, children: tableData.map((row, i) => {
172
+ const total = tableData.reduce((s, r) => s + parseFloat(r.q1.replace(/[$,]/g, '')) + parseFloat(r.q2.replace(/[$,]/g, ''))
173
+ + parseFloat(r.q3.replace(/[$,]/g, '')) + parseFloat(r.q4.replace(/[$,]/g, '')), 0);
174
+ const rowTotal = parseFloat(row.q1.replace(/[$,]/g, '')) + parseFloat(row.q2.replace(/[$,]/g, ''))
175
+ + parseFloat(row.q3.replace(/[$,]/g, '')) + parseFloat(row.q4.replace(/[$,]/g, ''));
176
+ const pct = ((rowTotal / total) * 100).toFixed(0);
177
+ return (_jsxs(View, { style: { flexDirection: 'row', alignItems: 'center', gap: 3 }, children: [_jsx(View, { style: { width: 8, height: 8, borderRadius: 2, backgroundColor: CHART_COLORS[i % CHART_COLORS.length] } }), _jsxs(Text, { style: { fontSize: 7, color: '#64748b' }, children: [row.region, " ", pct, "%"] })] }, i));
178
+ }) })] })] })] }), _jsx(Text, { style: styles.chartTitle, children: "Quarterly Growth Trend" }), _jsxs(View, { style: { backgroundColor: '#f8fafc', borderRadius: 4, borderWidth: 1, borderColor: '#e2e8f0', paddingVertical: 12, marginBottom: 24 }, children: [_jsx(Svg, { width: 484, height: 140, viewBox: "0 0 484 140", content: renderLineChart(tableData) }), _jsx(View, { style: { position: 'relative', height: 14, marginTop: 4 }, children: ['Q1', 'Q2', 'Q3', 'Q4'].map((q, i) => (_jsx(Text, { style: { position: 'absolute', left: 16 + (452 / 3) * i - 12, width: 24, fontSize: 8, color: '#64748b', textAlign: 'center' }, children: q }, i))) })] }), _jsx(PageBreak, {}), _jsxs(Text, { bookmark: data.sections[3].title, style: styles.sectionTitle, children: ["4. ", data.sections[3].title] }), _jsx(Text, { style: styles.introText, children: data.sections[3].intro }), (data.sections[3].items || []).map((item, i) => (_jsxs(View, { style: styles.recCard, children: [_jsx(View, { style: styles.recBadge, children: _jsx(Text, { style: styles.recBadgeText, children: i + 1 }) }), _jsxs(View, { style: { flexGrow: 1, flexShrink: 1 }, children: [_jsx(Text, { style: styles.recTitle, children: item.title }), _jsx(Text, { style: styles.recBody, children: item.description }), _jsxs(View, { style: { flexDirection: 'row', gap: 16, marginTop: 8 }, children: [_jsxs(View, { style: { flexDirection: 'row', gap: 4 }, children: [_jsx(Text, { style: styles.recLabel, children: "Priority:" }), _jsx(Text, { style: styles.recValue, children: item.priority })] }), _jsxs(View, { style: { flexDirection: 'row', gap: 4 }, children: [_jsx(Text, { style: styles.recLabel, children: "Timeline:" }), _jsx(Text, { style: styles.recValue, children: item.timeline })] })] })] })] }, i)))] })] }));
179
+ }
@@ -0,0 +1 @@
1
+ export default function ShippingLabel(data: any): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Document, Page, View, Text } from '@formepdf/react';
3
+ export default function ShippingLabel(data) {
4
+ return (_jsx(Document, { title: `Shipping Label - ${data.tracking}`, children: _jsxs(Page, { size: { width: 288, height: 432 }, margin: 16, children: [_jsxs(View, { style: { marginBottom: 12, padding: 8 }, children: [_jsx(Text, { style: { fontSize: 7, fontWeight: 700, color: '#64748b', textTransform: 'uppercase', letterSpacing: 1, marginBottom: 4 }, children: "From" }), _jsx(Text, { style: { fontSize: 8, color: '#334155' }, children: data.from.name }), _jsx(Text, { style: { fontSize: 8, color: '#334155' }, children: data.from.address }), _jsx(Text, { style: { fontSize: 8, color: '#334155' }, children: data.from.cityStateZip })] }), _jsx(View, { style: { borderTopWidth: 2, borderColor: '#0f172a', marginBottom: 12 } }), _jsxs(View, { style: { padding: 12, marginBottom: 12 }, children: [_jsx(Text, { style: { fontSize: 7, fontWeight: 700, color: '#64748b', textTransform: 'uppercase', letterSpacing: 1, marginBottom: 8 }, children: "To" }), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#0f172a' }, children: data.to.name }), _jsx(Text, { style: { fontSize: 10, color: '#0f172a', marginTop: 4 }, children: data.to.address }), data.to.address2 && (_jsx(Text, { style: { fontSize: 10, color: '#0f172a' }, children: data.to.address2 })), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#0f172a', marginTop: 2 }, children: data.to.cityStateZip })] }), _jsxs(View, { style: { marginBottom: 8, padding: 8 }, children: [_jsx(View, { style: { alignItems: 'center', marginBottom: 8 }, children: _jsxs(View, { style: { flexDirection: 'row', gap: 2 }, children: [_jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 3, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 3, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 3, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 3, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 3, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 3, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 2, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 1, height: 40, backgroundColor: '#0f172a' } }), _jsx(View, { style: { width: 3, height: 40, backgroundColor: '#0f172a' } })] }) }), _jsx(Text, { style: { fontSize: 8, fontWeight: 700, color: '#0f172a', letterSpacing: 2, textAlign: 'center' }, children: data.tracking })] }), _jsx(View, { style: { borderTopWidth: 1, borderColor: '#cbd5e1', marginBottom: 8 } }), _jsxs(View, { style: { flexDirection: 'row', justifyContent: 'space-between', padding: 8 }, children: [_jsxs(View, { style: { flex: 1 }, children: [_jsx(Text, { style: { fontSize: 7, color: '#64748b', textTransform: 'uppercase', letterSpacing: 1 }, children: "Weight" }), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#0f172a', marginTop: 2 }, children: data.weight })] }), _jsxs(View, { style: { flex: 2, justifyContent: 'center', alignItems: 'center' }, children: [_jsx(Text, { style: { width: '100%', textAlign: 'center', fontSize: 7, color: '#64748b', textTransform: 'uppercase', letterSpacing: 1 }, children: "Dimensions" }), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#0f172a', marginTop: 2 }, children: data.dimensions })] }), _jsxs(View, { style: { flex: 1, justifyContent: 'center', alignItems: 'flex-end' }, children: [_jsx(Text, { style: { width: '100%', textAlign: 'right', fontSize: 7, color: '#64748b', textTransform: 'uppercase', letterSpacing: 1 }, children: "Service" }), _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#0f172a', marginTop: 2 }, children: data.service })] })] }), data.stamps && data.stamps.length > 0 && (_jsx(View, { style: { flexDirection: 'row', gap: 8, marginTop: 8, padding: 8 }, children: data.stamps.map((stamp, i) => (_jsx(View, { style: { flex: 1, textAlign: 'center', paddingVertical: 6, paddingHorizontal: 12, borderWidth: 2, borderColor: '#dc2626', borderRadius: 2 }, children: _jsx(Text, { style: { fontSize: 10, fontWeight: 700, color: '#dc2626', textTransform: 'uppercase' }, children: stamp }) }, i))) }))] }) }));
5
+ }
@@ -0,0 +1,37 @@
1
+ import type { ReactElement } from 'react';
2
+ interface BaseOptions {
3
+ resendApiKey: string;
4
+ from: string;
5
+ to: string | string[];
6
+ subject: string;
7
+ filename?: string;
8
+ html?: string;
9
+ text?: string;
10
+ react?: ReactElement;
11
+ cc?: string | string[];
12
+ bcc?: string | string[];
13
+ replyTo?: string;
14
+ tags?: {
15
+ name: string;
16
+ value: string;
17
+ }[];
18
+ headers?: Record<string, string>;
19
+ }
20
+ interface TemplateOptions extends BaseOptions {
21
+ template: string;
22
+ data?: Record<string, any>;
23
+ render?: never;
24
+ }
25
+ interface RenderOptions extends BaseOptions {
26
+ template?: never;
27
+ data?: never;
28
+ render: () => ReactElement;
29
+ }
30
+ export type SendPdfOptions = TemplateOptions | RenderOptions;
31
+ export interface RenderAttachOptions {
32
+ template?: string;
33
+ data?: Record<string, any>;
34
+ render?: () => ReactElement;
35
+ filename?: string;
36
+ }
37
+ export {};
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@formepdf/resend",
3
+ "version": "0.5.0",
4
+ "description": "Render a PDF and email it in one call — Forme + Resend",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "dependencies": {
15
+ "@formepdf/react": "0.5.0",
16
+ "@formepdf/core": "0.5.0"
17
+ },
18
+ "peerDependencies": {
19
+ "resend": ">=3.0.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^22.0.0",
23
+ "react": "^19.0.0",
24
+ "resend": "^4.0.0",
25
+ "typescript": "^5.7.0"
26
+ },
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/formepdf/forme",
31
+ "directory": "packages/resend"
32
+ }
33
+ }