From 51aec8d8b311d6108fb014aa98dd96a1332f4729 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 4 Nov 2024 12:55:12 +0200 Subject: [PATCH] feat: render server-side invoice pdf template using React server --- packages/server/package.json | 4 +- .../api/controllers/Sales/SalesInvoices.ts | 4 +- .../services/Sales/Invoices/SaleInvoicePdf.ts | 19 +- pnpm-lock.yaml | 64 +--- shared/pdf-templates/src/Test.tsx | 3 - .../src/components/InvoicePaperTemplate.tsx | 343 ++++++++++++++++++ .../src/components/PaperTemplate.tsx | 242 ++++++++++++ .../src/components/PaperTemplateLayout.tsx | 25 ++ .../src/components/_constants.ts | 25 ++ shared/pdf-templates/src/index.ts | 3 +- shared/pdf-templates/src/lib/layout/Box.tsx | 19 + shared/pdf-templates/src/lib/layout/Group.tsx | 58 +++ shared/pdf-templates/src/lib/layout/Stack.tsx | 33 ++ shared/pdf-templates/src/lib/layout/utils.ts | 5 + shared/pdf-templates/src/lib/main.ts | 4 + shared/pdf-templates/src/lib/text/Text.tsx | 5 + shared/pdf-templates/src/vite-env.d.ts | 1 - 17 files changed, 787 insertions(+), 70 deletions(-) delete mode 100644 shared/pdf-templates/src/Test.tsx create mode 100644 shared/pdf-templates/src/components/InvoicePaperTemplate.tsx create mode 100644 shared/pdf-templates/src/components/PaperTemplate.tsx create mode 100644 shared/pdf-templates/src/components/PaperTemplateLayout.tsx create mode 100644 shared/pdf-templates/src/components/_constants.ts create mode 100644 shared/pdf-templates/src/lib/layout/Box.tsx create mode 100644 shared/pdf-templates/src/lib/layout/Group.tsx create mode 100644 shared/pdf-templates/src/lib/layout/Stack.tsx create mode 100644 shared/pdf-templates/src/lib/layout/utils.ts create mode 100644 shared/pdf-templates/src/lib/main.ts create mode 100644 shared/pdf-templates/src/lib/text/Text.tsx delete mode 100644 shared/pdf-templates/src/vite-env.d.ts 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) => ( + + ))} + + + + + {data.map((_data: any) => ( + + {columns.map((column, index) => ( + + ))} + + ))} + +
+ {col.label} +
+ {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 @@ -///