diff --git a/packages/server/package.json b/packages/server/package.json
index 77648c179..050bbba3b 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -20,11 +20,11 @@
"bigcapital": "./bin/bigcapital.js"
},
"dependencies": {
- "@aws-sdk/client-s3": "^3.576.0",
- "@aws-sdk/s3-request-presigner": "^3.583.0",
"@bigcapital/utils": "*",
"@bigcapital/email-components": "*",
"@bigcapital/pdf-templates": "*",
+ "@aws-sdk/client-s3": "^3.576.0",
+ "@aws-sdk/s3-request-presigner": "^3.583.0",
"@casl/ability": "^5.4.3",
"@hapi/boom": "^7.4.3",
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
diff --git a/packages/server/src/api/controllers/Sales/SalesInvoices.ts b/packages/server/src/api/controllers/Sales/SalesInvoices.ts
index c0c0bf54c..4393b21ff 100644
--- a/packages/server/src/api/controllers/Sales/SalesInvoices.ts
+++ b/packages/server/src/api/controllers/Sales/SalesInvoices.ts
@@ -451,14 +451,14 @@ export default class SaleInvoicesController extends BaseController {
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves invoice in pdf format.
- if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) {
+ if (true) {
const [pdfContent, filename] =
await this.saleInvoiceApplication.saleInvoicePdf(
tenantId,
saleInvoiceId
);
res.set({
- 'Content-Type': 'application/pdf',
+ 'Content-Type': 'text/html',
'Content-Length': pdfContent.length,
'Content-Disposition': `attachment; filename="${filename}"`,
});
diff --git a/packages/server/src/services/Sales/Invoices/SaleInvoicePdf.ts b/packages/server/src/services/Sales/Invoices/SaleInvoicePdf.ts
index d2c78a8b4..c2e66557a 100644
--- a/packages/server/src/services/Sales/Invoices/SaleInvoicePdf.ts
+++ b/packages/server/src/services/Sales/Invoices/SaleInvoicePdf.ts
@@ -1,4 +1,5 @@
import { Inject, Service } from 'typedi';
+import { renderInvoicePaperTemplateHtml } from '@bigcapital/pdf-templates';
import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy';
import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable';
import { GetSaleInvoice } from './GetSaleInvoice';
@@ -8,6 +9,7 @@ import { InvoicePdfTemplateAttributes } from '@/interfaces';
import { SaleInvoicePdfTemplate } from './SaleInvoicePdfTemplate';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events';
+import { renderInvoicePaymentEmail } from '@bigcapital/email-components';
@Service()
export class SaleInvoicePdf {
@@ -45,11 +47,16 @@ export class SaleInvoicePdf {
tenantId,
invoiceId
);
- const htmlContent = await this.templateInjectable.render(
- tenantId,
- 'modules/invoice-standard',
- brandingAttributes
- );
+ // const htmlContent = await this.templateInjectable.render(
+ // tenantId,
+ // 'modules/invoice-standard',
+ // brandingAttributes
+ // );
+
+ const htmlContent = renderInvoicePaperTemplateHtml({});
+
+ console.log(htmlContent);
+
// Converts the given html content to pdf document.
const buffer = await this.chromiumlyTenancy.convertHtmlContent(
tenantId,
@@ -62,7 +69,7 @@ export class SaleInvoicePdf {
events.saleInvoice.onPdfViewed,
eventPayload
);
- return [buffer, filename];
+ return [htmlContent, filename];
}
/**
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 779d86d89..ffd1f0ef9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -5991,7 +5991,7 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
ajv: 6.12.6
- debug: 4.3.4
+ debug: 4.3.7(supports-color@5.5.0)
espree: 9.6.1
globals: 13.24.0
ignore: 5.3.1
@@ -6138,7 +6138,7 @@ packages:
engines: {node: '>=10.10.0'}
dependencies:
'@humanwhocodes/object-schema': 2.0.3
- debug: 4.3.4
+ debug: 4.3.7(supports-color@5.5.0)
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -6730,7 +6730,7 @@ packages:
engines: {node: '>=18'}
dev: false
- /@lerna/create@8.1.3(typescript@4.9.5):
+ /@lerna/create@8.1.3(typescript@5.6.3):
resolution: {integrity: sha512-JFvIYrlvR8Txa8h7VZx8VIQDltukEKOKaZL/muGO7Q/5aE2vjOKHsD/jkWYe/2uFy1xv37ubdx17O1UXQNadPg==}
engines: {node: '>=18.0.0'}
dependencies:
@@ -6745,7 +6745,7 @@ packages:
columnify: 1.6.0
conventional-changelog-core: 5.0.1
conventional-recommended-bump: 7.0.1
- cosmiconfig: 8.3.6(typescript@4.9.5)
+ cosmiconfig: 8.3.6(typescript@5.6.3)
dedent: 0.7.0
execa: 5.0.0
fs-extra: 11.2.0
@@ -15672,22 +15672,6 @@ packages:
path-type: 4.0.0
yaml: 1.10.2
- /cosmiconfig@8.3.6(typescript@4.9.5):
- resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
- engines: {node: '>=14'}
- peerDependencies:
- typescript: '>=4.9.5'
- peerDependenciesMeta:
- typescript:
- optional: true
- dependencies:
- import-fresh: 3.3.0
- js-yaml: 4.1.0
- parse-json: 5.2.0
- path-type: 4.0.0
- typescript: 4.9.5
- dev: true
-
/cosmiconfig@8.3.6(typescript@5.6.3):
resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
engines: {node: '>=14'}
@@ -23180,7 +23164,7 @@ packages:
engines: {node: '>=18.0.0'}
hasBin: true
dependencies:
- '@lerna/create': 8.1.3(typescript@4.9.5)
+ '@lerna/create': 8.1.3(typescript@5.6.3)
'@npmcli/run-script': 7.0.2
'@nx/devkit': 19.0.7(nx@19.0.7)
'@octokit/plugin-enterprise-rest': 6.0.1
@@ -23193,7 +23177,7 @@ packages:
conventional-changelog-angular: 7.0.0
conventional-changelog-core: 5.0.1
conventional-recommended-bump: 7.0.1
- cosmiconfig: 8.3.6(typescript@4.9.5)
+ cosmiconfig: 8.3.6(typescript@5.6.3)
dedent: 0.7.0
envinfo: 7.8.1
execa: 5.0.0
@@ -23245,7 +23229,7 @@ packages:
strong-log-transformer: 2.1.0
tar: 6.2.1
temp-dir: 1.0.0
- typescript: 4.9.5
+ typescript: 5.6.3
upath: 2.0.1
uuid: 9.0.1
validate-npm-package-license: 3.0.4
@@ -28673,7 +28657,7 @@ packages:
semver: 7.6.2
source-map-loader: 3.0.2(webpack@5.91.0)
style-loader: 3.3.4(webpack@5.91.0)
- tailwindcss: 3.4.3(ts-node@10.9.2)
+ tailwindcss: 3.4.14(ts-node@10.9.2)
terser-webpack-plugin: 5.3.10(esbuild@0.23.1)(webpack@5.91.0)
typescript: 4.9.5
webpack: 5.91.0(esbuild@0.23.1)
@@ -31440,37 +31424,6 @@ packages:
- ts-node
dev: false
- /tailwindcss@3.4.3(ts-node@10.9.2):
- resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==}
- engines: {node: '>=14.0.0'}
- hasBin: true
- dependencies:
- '@alloc/quick-lru': 5.2.0
- arg: 5.0.2
- chokidar: 3.6.0
- didyoumean: 1.2.2
- dlv: 1.1.3
- fast-glob: 3.3.2
- glob-parent: 6.0.2
- is-glob: 4.0.3
- jiti: 1.21.0
- lilconfig: 2.1.0
- micromatch: 4.0.7
- normalize-path: 3.0.0
- object-hash: 3.0.0
- picocolors: 1.1.1
- postcss: 8.4.47
- postcss-import: 15.1.0(postcss@8.4.47)
- postcss-js: 4.0.1(postcss@8.4.47)
- postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2)
- postcss-nested: 6.0.1(postcss@8.4.47)
- postcss-selector-parser: 6.1.0
- resolve: 1.22.8
- sucrase: 3.35.0
- transitivePeerDependencies:
- - ts-node
- dev: false
-
/tapable@0.1.10:
resolution: {integrity: sha512-jX8Et4hHg57mug1/079yitEKWGB3LCwoxByLsNim89LABq8NqgiX+6iYVOsq0vX8uJHkU+DZ5fnq95f800bEsQ==}
engines: {node: '>=0.6'}
@@ -32439,6 +32392,7 @@ packages:
resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
engines: {node: '>=4.2.0'}
hasBin: true
+ dev: false
/typescript@5.4.2:
resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==}
diff --git a/shared/pdf-templates/src/Test.tsx b/shared/pdf-templates/src/Test.tsx
deleted file mode 100644
index 20796aca0..000000000
--- a/shared/pdf-templates/src/Test.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export const Test = () => {
- return
asdasd
;
-};
diff --git a/shared/pdf-templates/src/components/InvoicePaperTemplate.tsx b/shared/pdf-templates/src/components/InvoicePaperTemplate.tsx
new file mode 100644
index 000000000..2d29c0db9
--- /dev/null
+++ b/shared/pdf-templates/src/components/InvoicePaperTemplate.tsx
@@ -0,0 +1,343 @@
+import { renderToString } from 'react-dom/server';
+import {
+ PaperTemplate,
+ PaperTemplateProps,
+ PaperTemplateTotalBorder,
+} from './PaperTemplate';
+import { x } from '@xstyled/emotion';
+import { Box } from '../lib/layout/Box';
+import { Text } from '../lib/text/Text';
+import { Stack } from '../lib/layout/Stack';
+import { Group } from '../lib/layout/Group';
+import {
+ DefaultPdfTemplateTerms,
+ DefaultPdfTemplateItemDescription,
+ DefaultPdfTemplateStatement,
+ DefaultPdfTemplateItemName,
+ DefaultPdfTemplateAddressBilledTo,
+ DefaultPdfTemplateAddressBilledFrom,
+} from './_constants';
+import { PaperTemplateLayout } from './PaperTemplateLayout';
+import createCache from '@emotion/cache';
+
+interface PapaerLine {
+ item?: string;
+ description?: string;
+ quantity?: string;
+ rate?: string;
+ total?: string;
+}
+
+interface PaperTax {
+ label: string;
+ amount: string;
+}
+
+export interface InvoicePaperTemplateProps extends PaperTemplateProps {
+ primaryColor?: string;
+ secondaryColor?: string;
+
+ showCompanyLogo?: boolean;
+ companyLogoUri?: string;
+
+ showInvoiceNumber?: boolean;
+ invoiceNumber?: string;
+ invoiceNumberLabel?: string;
+
+ showDateIssue?: boolean;
+ dateIssue?: string;
+ dateIssueLabel?: string;
+
+ showDueDate?: boolean;
+ dueDate?: string;
+ dueDateLabel?: string;
+
+ companyName?: string;
+ bigtitle?: string;
+
+ // Address
+ showCustomerAddress?: boolean;
+ customerAddress?: string;
+
+ showCompanyAddress?: boolean;
+ companyAddress?: string;
+
+ billedToLabel?: string;
+
+ // Entries
+ lineItemLabel?: string;
+ lineQuantityLabel?: string;
+ lineRateLabel?: string;
+ lineTotalLabel?: string;
+
+ // Totals
+ showTotal?: boolean;
+ totalLabel?: string;
+ total?: string;
+
+ showDiscount?: boolean;
+ discountLabel?: string;
+ discount?: string;
+
+ showSubtotal?: boolean;
+ subtotalLabel?: string;
+ subtotal?: string;
+
+ showPaymentMade?: boolean;
+ paymentMadeLabel?: string;
+ paymentMade?: string;
+
+ showTaxes?: boolean;
+
+ showDueAmount?: boolean;
+ showBalanceDue?: boolean;
+ balanceDueLabel?: string;
+ balanceDue?: string;
+
+ // Footer
+ termsConditionsLabel?: string;
+ showTermsConditions?: boolean;
+ termsConditions?: string;
+
+ statementLabel?: string;
+ showStatement?: boolean;
+ statement?: string;
+
+ lines?: Array;
+ taxes?: Array;
+}
+
+export function InvoicePaperTemplate({
+ primaryColor,
+ secondaryColor,
+
+ companyName = 'Bigcapital Technology, Inc.',
+
+ showCompanyLogo = true,
+ companyLogoUri = '',
+
+ dueDate = 'September 3, 2024',
+ dueDateLabel = 'Date due',
+ showDueDate = true,
+
+ dateIssue = 'September 3, 2024',
+ dateIssueLabel = 'Date of issue',
+ showDateIssue = true,
+
+ // dateIssue,
+ invoiceNumberLabel = 'Invoice number',
+ invoiceNumber = '346D3D40-0001',
+ showInvoiceNumber = true,
+
+ // Address
+ showCustomerAddress = true,
+ customerAddress = DefaultPdfTemplateAddressBilledTo,
+
+ showCompanyAddress = true,
+ companyAddress = DefaultPdfTemplateAddressBilledFrom,
+
+ billedToLabel = 'Billed To',
+
+ // Entries
+ lineItemLabel = 'Item',
+ lineQuantityLabel = 'Qty',
+ lineRateLabel = 'Rate',
+ lineTotalLabel = 'Total',
+
+ totalLabel = 'Total',
+ subtotalLabel = 'Subtotal',
+ discountLabel = 'Discount',
+ paymentMadeLabel = 'Payment Made',
+ balanceDueLabel = 'Balance Due',
+
+ // Totals
+ showTotal = true,
+ showSubtotal = true,
+ showDiscount = true,
+ showTaxes = true,
+ showPaymentMade = true,
+ showDueAmount = true,
+ showBalanceDue = true,
+
+ total = '$662.75',
+ subtotal = '630.00',
+ discount = '0.00',
+ paymentMade = '100.00',
+ balanceDue = '$562.75',
+
+ // Footer paragraphs.
+ termsConditionsLabel = 'Terms & Conditions',
+ showTermsConditions = true,
+ termsConditions = DefaultPdfTemplateTerms,
+
+ lines = [
+ {
+ item: DefaultPdfTemplateItemName,
+ description: DefaultPdfTemplateItemDescription,
+ rate: '1',
+ quantity: '1000',
+ total: '$1000.00',
+ },
+ ],
+ taxes = [
+ { label: 'Sample Tax1 (4.70%)', amount: '11.75' },
+ { label: 'Sample Tax2 (7.00%)', amount: '21.74' },
+ ],
+
+ statementLabel = 'Statement',
+ showStatement = true,
+ statement = DefaultPdfTemplateStatement,
+ ...props
+}: InvoicePaperTemplateProps) {
+ return (
+
+
+
+
+
+
+
+ {showInvoiceNumber && (
+
+ {invoiceNumber}
+
+ )}
+ {showDateIssue && (
+
+ {dateIssue}
+
+ )}
+ {showDueDate && (
+
+ {dueDate}
+
+ )}
+
+
+
+ {companyLogoUri && showCompanyLogo && (
+
+ )}
+
+
+
+ {showCompanyAddress && (
+
+
+
+ )}
+ {showCustomerAddress && (
+
+ {billedToLabel}
+
+
+ )}
+
+
+
+ (
+
+ {data.item}
+
+ {data.description}
+
+
+ ),
+ },
+ { label: lineQuantityLabel, accessor: 'quantity' },
+ { label: lineRateLabel, accessor: 'rate', align: 'right' },
+ { label: lineTotalLabel, accessor: 'total', align: 'right' },
+ ]}
+ data={lines}
+ />
+
+ {showSubtotal && (
+
+ )}
+ {showDiscount && (
+
+ )}
+ {showTaxes && (
+ <>
+ {taxes.map((tax, index) => (
+
+ ))}
+ >
+ )}
+ {showTotal && (
+
+ )}
+ {showPaymentMade && (
+
+ )}
+ {showBalanceDue && (
+
+ )}
+
+
+
+
+ {showTermsConditions && termsConditions && (
+
+ {termsConditions}
+
+ )}
+
+ {showStatement && statement && (
+
+ {statement}
+
+ )}
+
+
+
+ );
+}
+
+export const renderInvoicePaperTemplateHtml = (
+ props: InvoicePaperTemplateProps
+) => {
+ const key = 'custom';
+ const cache = createCache({ key });
+
+ return renderToString(
+
+
+
+ );
+};
diff --git a/shared/pdf-templates/src/components/PaperTemplate.tsx b/shared/pdf-templates/src/components/PaperTemplate.tsx
new file mode 100644
index 000000000..73bd4f73a
--- /dev/null
+++ b/shared/pdf-templates/src/components/PaperTemplate.tsx
@@ -0,0 +1,242 @@
+import React from 'react';
+import clsx from 'classnames';
+import { get, isFunction } from 'lodash';
+import { x } from '@xstyled/emotion';
+import { Box, BoxProps } from '../lib/layout/Box';
+import { Group, GroupProps } from '../lib/layout/Group';
+
+const styles = {
+ root: 'root',
+ bigTitle: 'bigTitle',
+ logoWrap: 'logoWrap',
+ logoImg: 'logoImg',
+ table: 'table',
+ tableBody: 'tableBody',
+ totals: 'totals',
+ totalsItem: 'totalsItem',
+ totalBottomBordered: 'totalBottomBordered',
+ totalBottomGrayBordered: 'totalBottomGrayBordered',
+ totalsItemLabel: 'totalsItemLabel',
+ totalsItemAmount: 'totalsItemAmount',
+ addressRoot: 'addressRoot',
+ paragraph: 'paragraph',
+ paragraphLabel: 'paragraphLabel',
+ details: 'details',
+ detail: 'detail',
+ detailLabel: 'detailLabel',
+};
+
+export interface PaperTemplateProps extends BoxProps {
+ primaryColor?: string;
+ secondaryColor?: string;
+ children?: React.ReactNode;
+}
+
+export function PaperTemplate({
+ primaryColor,
+ secondaryColor,
+ children,
+ ...restProps
+}: PaperTemplateProps) {
+ return (
+
+
+ {children}
+
+ );
+}
+
+interface PaperTemplateTableProps {
+ columns: Array<{
+ accessor: string | ((data: Record) => JSX.Element);
+ label: string;
+ value?: JSX.Element;
+ align?: 'left' | 'center' | 'right';
+ }>;
+ data: Array>;
+}
+
+interface PaperTemplateBigTitleProps {
+ title: string;
+}
+
+PaperTemplate.BigTitle = ({ title }: PaperTemplateBigTitleProps) => {
+ return (
+
+ {title}
+
+ );
+};
+
+interface PaperTemplateLogoProps {
+ logoUri: string;
+}
+
+PaperTemplate.Logo = ({ logoUri }: PaperTemplateLogoProps) => {
+ return (
+
+

+
+ );
+};
+
+PaperTemplate.Table = ({ columns, data }: PaperTemplateTableProps) => {
+ return (
+
+
+
+ {columns.map((col, index) => (
+ |
+ {col.label}
+ |
+ ))}
+
+
+
+
+ {data.map((_data: any) => (
+
+ {columns.map((column, index) => (
+ |
+ {isFunction(column?.accessor)
+ ? column?.accessor(_data)
+ : get(_data, column.accessor)}
+ |
+ ))}
+
+ ))}
+
+
+ );
+};
+
+export enum PaperTemplateTotalBorder {
+ Gray = 'gray',
+ Dark = 'dark',
+}
+
+PaperTemplate.Totals = ({ children }: { children: React.ReactNode }) => {
+ return (
+
+ {children}
+ );
+};
+PaperTemplate.TotalLine = ({
+ label,
+ amount,
+ border,
+ style,
+}: {
+ label: string;
+ amount: string;
+ border?: PaperTemplateTotalBorder;
+ style?: any;
+}) => {
+ return (
+
+ {label}
+ {amount}
+
+ );
+};
+
+PaperTemplate.MutedText = () => { };
+
+PaperTemplate.Text = () => { };
+
+PaperTemplate.AddressesGroup = (props: GroupProps) => {
+ return (
+
+ );
+};
+PaperTemplate.Address = ({ children }: { children: React.ReactNode }) => {
+ return {children};
+};
+
+PaperTemplate.Statement = ({
+ label,
+ children,
+}: {
+ label: string;
+ children: React.ReactNode;
+}) => {
+ return (
+
+ {label &&
{label}
}
+
{children}
+
+ );
+};
+
+PaperTemplate.TermsList = ({ children }: { children: React.ReactNode }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+PaperTemplate.TermsItem = ({
+ label,
+ children,
+}: {
+ label: string;
+ children: React.ReactNode;
+}) => {
+ return (
+
+ {label}
+ {children}
+
+ );
+};
diff --git a/shared/pdf-templates/src/components/PaperTemplateLayout.tsx b/shared/pdf-templates/src/components/PaperTemplateLayout.tsx
new file mode 100644
index 000000000..d2e767837
--- /dev/null
+++ b/shared/pdf-templates/src/components/PaperTemplateLayout.tsx
@@ -0,0 +1,25 @@
+import { CacheProvider, ThemeProvider } from '@emotion/react';
+import { EmotionCache } from '@emotion/cache';
+import { defaultTheme } from '@xstyled/system';
+
+const theme = {
+ ...defaultTheme,
+};
+export function PaperTemplateLayout({ cache, children }: {
+ children: React.ReactNode;
+ cache: EmotionCache;
+}) {
+ const html = (
+
+ {children}
+
+ );
+
+ return (
+
+
+ {html}
+
+
+ );
+}
diff --git a/shared/pdf-templates/src/components/_constants.ts b/shared/pdf-templates/src/components/_constants.ts
new file mode 100644
index 000000000..c08cce9c5
--- /dev/null
+++ b/shared/pdf-templates/src/components/_constants.ts
@@ -0,0 +1,25 @@
+export const DefaultPdfTemplateTerms =
+ 'All services provided are non-refundable. For any disputes, please contact us within 7 days of receiving this invoice.';
+
+export const DefaultPdfTemplateStatement =
+ 'Thank you for your business. We look forward to working with you again!';
+
+export const DefaultPdfTemplateItemName = 'Web development';
+
+export const DefaultPdfTemplateItemDescription =
+ 'Website development with content and SEO optimization';
+
+export const DefaultPdfTemplateAddressBilledTo = `Bigcapital Technology, Inc.
+131 Continental Dr,
+Suite 305,
+Newark, Delaware 19713,
+United States,
++1 762-339-5634
+`;
+
+export const DefaultPdfTemplateAddressBilledFrom = `131 Continental Dr Suite 305 Newark,
+Delaware 19713,
+United States,
++1 762-339-5634,
+ahmed@bigcapital.app
+`;
diff --git a/shared/pdf-templates/src/index.ts b/shared/pdf-templates/src/index.ts
index c8fee7bb8..8a15d72ed 100644
--- a/shared/pdf-templates/src/index.ts
+++ b/shared/pdf-templates/src/index.ts
@@ -1 +1,2 @@
-export * from './Test';
+export * from './components/PaperTemplate';
+export * from './components/InvoicePaperTemplate';
diff --git a/shared/pdf-templates/src/lib/layout/Box.tsx b/shared/pdf-templates/src/lib/layout/Box.tsx
new file mode 100644
index 000000000..2c05008a9
--- /dev/null
+++ b/shared/pdf-templates/src/lib/layout/Box.tsx
@@ -0,0 +1,19 @@
+import React, { forwardRef, Ref } from 'react';
+import { SystemProps, x } from '@xstyled/emotion';
+
+interface IProps {
+ className?: string;
+}
+export interface BoxProps
+ extends SystemProps,
+ IProps,
+ Omit, 'color' | 'as'> { }
+
+export const Box = forwardRef(
+ ({ className, ...rest }: BoxProps, ref: Ref) => {
+ const Element = x.div;
+
+ return ;
+ },
+);
+Box.displayName = '@bigcapital/Box';
diff --git a/shared/pdf-templates/src/lib/layout/Group.tsx b/shared/pdf-templates/src/lib/layout/Group.tsx
new file mode 100644
index 000000000..0165680de
--- /dev/null
+++ b/shared/pdf-templates/src/lib/layout/Group.tsx
@@ -0,0 +1,58 @@
+import React from 'react';
+import { SystemProps } from '@xstyled/emotion';
+import { Box } from './Box';
+import { filterFalsyChildren } from './utils';
+
+export type GroupPosition = 'right' | 'center' | 'left' | 'apart';
+
+export const GROUP_POSITIONS = {
+ left: 'flex-start',
+ center: 'center',
+ right: 'flex-end',
+ apart: 'space-between',
+};
+
+export interface GroupProps
+ extends SystemProps,
+ Omit, 'color'> {
+ /** Defines justify-content property */
+ position?: GroupPosition;
+
+ /** Defined flex-wrap property */
+ noWrap?: boolean;
+
+ /** Defines flex-grow property for each element, true -> 1, false -> 0 */
+ grow?: boolean;
+
+ /** Space between elements */
+ spacing?: number;
+
+ /** Defines align-items css property */
+ align?: React.CSSProperties['alignItems'];
+}
+
+export function Group({
+ position = 'left',
+ spacing = 20,
+ align = 'center',
+ noWrap,
+ children,
+ ...props
+}: GroupProps) {
+ const filteredChildren = filterFalsyChildren(children);
+
+ return (
+
+ {filteredChildren}
+
+ );
+}
diff --git a/shared/pdf-templates/src/lib/layout/Stack.tsx b/shared/pdf-templates/src/lib/layout/Stack.tsx
new file mode 100644
index 000000000..fc790e7c8
--- /dev/null
+++ b/shared/pdf-templates/src/lib/layout/Stack.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { x, SystemProps } from '@xstyled/emotion';
+
+export interface StackProps
+ extends SystemProps,
+ Omit, 'color'> {
+ /** Key of theme.spacing or number to set gap in px */
+ spacing?: number;
+
+ /** align-items CSS property */
+ align?: React.CSSProperties['alignItems'];
+
+ /** justify-content CSS property */
+ justify?: React.CSSProperties['justifyContent'];
+}
+
+export function Stack({
+ spacing = 20,
+ align = 'stretch',
+ justify = 'top',
+ ...restProps
+}: StackProps) {
+ return (
+
+ );
+}
diff --git a/shared/pdf-templates/src/lib/layout/utils.ts b/shared/pdf-templates/src/lib/layout/utils.ts
new file mode 100644
index 000000000..6850d1d73
--- /dev/null
+++ b/shared/pdf-templates/src/lib/layout/utils.ts
@@ -0,0 +1,5 @@
+import React from 'react';
+
+export const filterFalsyChildren = (children: React.ReactNode) => {
+ return React.Children.toArray(children).filter(Boolean);
+};
diff --git a/shared/pdf-templates/src/lib/main.ts b/shared/pdf-templates/src/lib/main.ts
new file mode 100644
index 000000000..ab06c84b9
--- /dev/null
+++ b/shared/pdf-templates/src/lib/main.ts
@@ -0,0 +1,4 @@
+export * from './layout/Stack';
+export * from './layout/Group';
+export * from './layout/Box';
+export * from './text/Text';
diff --git a/shared/pdf-templates/src/lib/text/Text.tsx b/shared/pdf-templates/src/lib/text/Text.tsx
new file mode 100644
index 000000000..eabe132cf
--- /dev/null
+++ b/shared/pdf-templates/src/lib/text/Text.tsx
@@ -0,0 +1,5 @@
+import { x } from '@xstyled/emotion';
+
+export const Text = ({ children }: { children: React.ReactNode }) => {
+ return {children};
+};
diff --git a/shared/pdf-templates/src/vite-env.d.ts b/shared/pdf-templates/src/vite-env.d.ts
deleted file mode 100644
index 11f02fe2a..000000000
--- a/shared/pdf-templates/src/vite-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-///