Merge branch 'develop' into add-pdf-templates-package

This commit is contained in:
Ahmed Bouhuolia
2024-11-05 17:19:38 +02:00
24 changed files with 315 additions and 247 deletions

View File

@@ -1,6 +1,9 @@
import { getUploadedObjectUri } from '@/services/Attachments/utils';
import TenantModel from 'models/TenantModel'; import TenantModel from 'models/TenantModel';
export class PdfTemplate extends TenantModel { export class PdfTemplate extends TenantModel {
public readonly attributes: Record<string, any>;
/** /**
* Table name. * Table name.
*/ */
@@ -47,7 +50,17 @@ export class PdfTemplate extends TenantModel {
* Virtual attributes. * Virtual attributes.
*/ */
static get virtualAttributes() { static get virtualAttributes() {
return []; return ['companyLogoUri'];
}
/**
* Retrieves the company logo uri.
* @returns {string}
*/
get companyLogoUri() {
return this.attributes.companyLogoKey
? getUploadedObjectUri(this.attributes.companyLogoKey)
: '';
} }
/** /**

View File

@@ -35,9 +35,12 @@ export class CreditNoteBrandingTemplate {
...defaultCreditNoteBrandingAttributes, ...defaultCreditNoteBrandingAttributes,
...commonOrgBrandingAttrs, ...commonOrgBrandingAttrs,
}; };
const brandingTemplateAttrs = {
...template.attributes,
companyLogoUri: template.companyLogoUri,
};
const attributes = mergePdfTemplateWithDefaultAttributes( const attributes = mergePdfTemplateWithDefaultAttributes(
template.attributes, brandingTemplateAttrs,
organizationBrandingAttrs organizationBrandingAttrs
); );
return { return {

View File

@@ -1,10 +1,9 @@
import { Transformer } from '@/lib/Transformer/Transformer'; import { Transformer } from '@/lib/Transformer/Transformer';
import { getTransactionTypeLabel } from '@/utils/transactions-types'; import { getTransactionTypeLabel } from '@/utils/transactions-types';
import { getUploadedObjectUri } from '../Attachments/utils';
export class GetPdfTemplateTransformer extends Transformer { export class GetPdfTemplateTransformer extends Transformer {
/** /**
* Includeded attributes. * Included attributes.
* @returns {string[]} * @returns {string[]}
*/ */
public includeAttributes = (): string[] => { public includeAttributes = (): string[] => {
@@ -44,20 +43,10 @@ export class GetPdfTemplateTransformer extends Transformer {
class GetPdfTemplateAttributesTransformer extends Transformer { class GetPdfTemplateAttributesTransformer extends Transformer {
/** /**
* Includeded attributes. * Included attributes.
* @returns {string[]} * @returns {string[]}
*/ */
public includeAttributes = (): string[] => { public includeAttributes = (): string[] => {
return ['companyLogoUri']; return [];
}; };
/**
* Retrieves the company logo uri.
* @returns {string}
*/
protected companyLogoUri(template) {
return template.companyLogoKey
? getUploadedObjectUri(template.companyLogoKey)
: '';
}
} }

View File

@@ -66,7 +66,6 @@ export interface ICreateInvoicePdfTemplateDTO {
showStatement?: boolean; showStatement?: boolean;
} }
export interface CommonOrganizationBrandingAttributes { export interface CommonOrganizationBrandingAttributes {
companyName?: string; companyName?: string;
primaryColor?: string; primaryColor?: string;

View File

@@ -41,7 +41,7 @@ export class GetInvoiceMailTemplateAttributesTransformer extends Transformer {
}; };
public companyLogoUri(): string { public companyLogoUri(): string {
return this.options.brandingTemplate?.attributes?.companyLogoUri; return this.options.brandingTemplate?.companyLogoUri;
} }
public companyName(): string { public companyName(): string {

View File

@@ -18,7 +18,6 @@ export class GetSaleInvoiceMailState {
/** /**
* Retrieves the invoice mail state of the given sale invoice. * Retrieves the invoice mail state of the given sale invoice.
* Invoice mail state includes the mail options, branding attributes and the invoice details. * Invoice mail state includes the mail options, branding attributes and the invoice details.
*
* @param {number} tenantId * @param {number} tenantId
* @param {number} saleInvoiceId * @param {number} saleInvoiceId
* @returns {Promise<SaleInvoiceMailState>} * @returns {Promise<SaleInvoiceMailState>}

View File

@@ -1,4 +1,3 @@
import { Transformer } from '@/lib/Transformer/Transformer';
import { SaleInvoiceTransformer } from './SaleInvoiceTransformer'; import { SaleInvoiceTransformer } from './SaleInvoiceTransformer';
import { ItemEntryTransformer } from './ItemEntryTransformer'; import { ItemEntryTransformer } from './ItemEntryTransformer';
@@ -11,6 +10,10 @@ export class GetSaleInvoiceMailStateTransformer extends SaleInvoiceTransformer {
return ['*']; return ['*'];
}; };
/**
* Included attributes.
* @returns {Array}
*/
public includeAttributes = (): string[] => { public includeAttributes = (): string[] => {
return [ return [
'invoiceDate', 'invoiceDate',
@@ -39,14 +42,26 @@ export class GetSaleInvoiceMailStateTransformer extends SaleInvoiceTransformer {
]; ];
}; };
/**
* Retrieves the company name.
* @returns {string}
*/
protected companyName = () => { protected companyName = () => {
return this.context.organization.name; return this.context.organization.name;
}; };
/**
* Retrieves the company logo uri.
* @returns {string | null}
*/
protected companyLogoUri = (invoice) => { protected companyLogoUri = (invoice) => {
return invoice.pdfTemplate?.attributes?.companyLogoUri; return invoice.pdfTemplate?.companyLogoUri;
}; };
/**
* Retrieves the primary color.
* @returns {string}
*/
protected primaryColor = (invoice) => { protected primaryColor = (invoice) => {
return invoice.pdfTemplate?.attributes?.primaryColor; return invoice.pdfTemplate?.attributes?.primaryColor;
}; };

View File

@@ -33,8 +33,12 @@ export class SaleEstimatePdfTemplate {
...defaultEstimatePdfBrandingAttributes, ...defaultEstimatePdfBrandingAttributes,
...commonOrgBrandingAttrs, ...commonOrgBrandingAttrs,
}; };
const brandingTemplateAttrs = {
...template.attributes,
companyLogoUri: template.companyLogoUri,
};
const attributes = mergePdfTemplateWithDefaultAttributes( const attributes = mergePdfTemplateWithDefaultAttributes(
template.attributes, brandingTemplateAttrs,
orgainizationBrandingAttrs orgainizationBrandingAttrs
); );
return { return {

View File

@@ -32,8 +32,12 @@ export class SaleInvoicePdfTemplate {
...defaultInvoicePdfTemplateAttributes, ...defaultInvoicePdfTemplateAttributes,
...commonOrgBrandingAttrs, ...commonOrgBrandingAttrs,
}; };
const brandingTemplateAttrs = {
...template.attributes,
companyLogoUri: template.companyLogoUri,
};
const attributes = mergePdfTemplateWithDefaultAttributes( const attributes = mergePdfTemplateWithDefaultAttributes(
template.attributes, brandingTemplateAttrs,
organizationBrandingAttrs organizationBrandingAttrs
); );
return { return {

View File

@@ -28,9 +28,7 @@ import { GetInvoicePaymentsService } from './GetInvoicePaymentsService';
import { SaleInvoiceNotifyBySms } from './SaleInvoiceNotifyBySms'; import { SaleInvoiceNotifyBySms } from './SaleInvoiceNotifyBySms';
import { SendInvoiceMailReminder } from './SendSaleInvoiceMailReminder'; import { SendInvoiceMailReminder } from './SendSaleInvoiceMailReminder';
import { SendSaleInvoiceMail } from './SendSaleInvoiceMail'; import { SendSaleInvoiceMail } from './SendSaleInvoiceMail';
import { GetSaleInvoiceMailReminder } from './GetSaleInvoiceMailReminder';
import { GetSaleInvoiceState } from './GetSaleInvoiceState'; import { GetSaleInvoiceState } from './GetSaleInvoiceState';
import { GetSaleInvoiceBrandTemplate } from './GetSaleInvoiceBrandTemplate';
import { GetSaleInvoiceMailState } from './GetSaleInvoiceMailState'; import { GetSaleInvoiceMailState } from './GetSaleInvoiceMailState';
@Service() @Service()
@@ -366,7 +364,10 @@ export class SaleInvoiceApplication {
* @param {number} saleInvoiceid * @param {number} saleInvoiceid
* @returns {Promise<SaleInvoiceMailState>} * @returns {Promise<SaleInvoiceMailState>}
*/ */
public getSaleInvoiceMailState(tenantId: number, saleInvoiceid: number) { public getSaleInvoiceMailState(
tenantId: number,
saleInvoiceid: number
): Promise<SaleInvoiceMailState> {
return this.getSaleInvoiceMailStateService.getInvoiceMailState( return this.getSaleInvoiceMailStateService.getInvoiceMailState(
tenantId, tenantId,
saleInvoiceid saleInvoiceid

View File

@@ -1,20 +1,19 @@
import config from '@/config'; import config from '@/config';
export const DEFAULT_INVOICE_MAIL_SUBJECT = export const DEFAULT_INVOICE_MAIL_SUBJECT =
'Invoice {Invoice Number} from {Company Name}'; 'Invoice {Invoice Number} from {Company Name} for {Customer Name}';
export const DEFAULT_INVOICE_MAIL_CONTENT = ` export const DEFAULT_INVOICE_MAIL_CONTENT = `Hi {Customer Name},
<p>Dear {Customer Name}</p>
<p>Thank you for your business, You can view or print your invoice from attachements.</p>
<p>
Invoice <strong>#{Invoice Number}</strong><br />
Due Date : <strong>{Invoice Due Date}</strong><br />
Amount : <strong>{Invoice Amount}</strong></br />
</p>
<p> Here's invoice # {Invoice Number} for {Invoice Amount}
<i>Regards</i><br />
<i>{Company Name}</i> The amount outstanding of {Invoice Due Amount} is due on {Invoice Due Date}.
</p>
From your online payment page you can print a PDF or view your outstanding bills.
If you have any questions, please let us know.
Thanks,
{Company Name}
`; `;
export const DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT = export const DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT =

View File

@@ -37,8 +37,12 @@ export class PaymentReceivedBrandingTemplate {
...defaultPaymentReceivedPdfTemplateAttributes, ...defaultPaymentReceivedPdfTemplateAttributes,
...commonOrgBrandingAttrs, ...commonOrgBrandingAttrs,
}; };
const brandingTemplateAttrs = {
...template.attributes,
companyLogoUri: template.companyLogoUri,
};
const attributes = mergePdfTemplateWithDefaultAttributes( const attributes = mergePdfTemplateWithDefaultAttributes(
template.attributes, brandingTemplateAttrs,
organizationBrandingAttrs organizationBrandingAttrs
); );
return { return {

View File

@@ -37,8 +37,12 @@ export class SaleReceiptBrandingTemplate {
...defaultSaleReceiptBrandingAttributes, ...defaultSaleReceiptBrandingAttributes,
...commonOrgBrandingAttrs, ...commonOrgBrandingAttrs,
}; };
const brandingTemplateAttrs = {
...template.attributes,
companyLogoUri: template.companyLogoUri,
};
const attributes = mergePdfTemplateWithDefaultAttributes( const attributes = mergePdfTemplateWithDefaultAttributes(
template.attributes, brandingTemplateAttrs,
organizationBrandingAttrs organizationBrandingAttrs
); );
return { return {

View File

@@ -5,7 +5,7 @@
"dependencies": { "dependencies": {
"@bigcapital/utils": "*", "@bigcapital/utils": "*",
"@bigcapital/pdf-templates": "*", "@bigcapital/pdf-templates": "*",
"@blueprintjs-formik/core": "^0.3.6", "@blueprintjs-formik/core": "^0.3.7",
"@blueprintjs-formik/datetime": "^0.3.7", "@blueprintjs-formik/datetime": "^0.3.7",
"@blueprintjs-formik/select": "^0.3.5", "@blueprintjs-formik/select": "^0.3.5",
"@blueprintjs/colors": "4.1.19", "@blueprintjs/colors": "4.1.19",

View File

@@ -93,6 +93,7 @@ export function InvoiceMailReceipt({
h="90px" h="90px"
w="90px" w="90px"
mx="auto" mx="auto"
borderRadius="3px"
backgroundRepeat="no-repeat" backgroundRepeat="no-repeat"
backgroundPosition="center center" backgroundPosition="center center"
backgroundSize="contain" backgroundSize="contain"

View File

@@ -1,45 +0,0 @@
import { useMemo } from 'react';
import { css } from '@emotion/css';
import { Box, } from '@/components';
import { InvoiceMailReceiptPreview } from '../InvoiceCustomize/InvoiceMailReceiptPreview';
import { useInvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
import { InvoiceSendMailPreviewWithHeader } from './InvoiceSendMailHeaderPreview';
import { useSendInvoiceMailMessage } from './_hooks';
export function InvoiceMailReceiptPreviewConneceted() {
const mailMessage = useSendInvoiceMailMessage();
const { invoiceMailState } = useInvoiceSendMailBoot();
const items = useMemo(
() =>
invoiceMailState?.entries?.map((entry: any) => ({
quantity: entry.quantity,
total: entry.totalFormatted,
label: entry.name,
})),
[invoiceMailState?.entries],
);
return (
<InvoiceSendMailPreviewWithHeader>
<Box px={4} pt={8} pb={16}>
<InvoiceMailReceiptPreview
companyName={invoiceMailState?.companyName}
// companyLogoUri={invoiceMailState?.companyLogoUri}
primaryColor={invoiceMailState?.primaryColor}
total={invoiceMailState?.totalFormatted}
dueDate={invoiceMailState?.dueDateFormatted}
dueAmount={invoiceMailState?.dueAmountFormatted}
invoiceNumber={invoiceMailState?.invoiceNo}
items={items}
message={mailMessage}
className={css`
margin: 0 auto;
`}
/>
</Box>
</InvoiceSendMailPreviewWithHeader>
);
}

View File

@@ -0,0 +1,64 @@
import { useMemo, ComponentType } from 'react';
import { css } from '@emotion/css';
import { Box } from '@/components';
import {
InvoiceMailReceiptPreview,
InvoiceMailReceiptPreviewProps,
} from '../InvoiceCustomize/InvoiceMailReceiptPreview';
import { useInvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
import { InvoiceSendMailPreviewWithHeader } from './InvoiceSendMailHeaderPreview';
import { useSendInvoiceMailMessage } from './_hooks';
export function InvoiceMailReceiptPreviewConnected() {
return (
<InvoiceSendMailPreviewWithHeader>
<Box px={4} pt={8} pb={16}>
<InvoiceMailReceiptPreviewWithProps
className={css`
margin: 0 auto;
`}
/>
</Box>
</InvoiceSendMailPreviewWithHeader>
);
}
/**
* Injects props from invoice mail state into the InvoiceMailReceiptPreview component.
*/
const withInvoiceMailReceiptPreviewProps = <
P extends InvoiceMailReceiptPreviewProps,
>(
WrappedComponent: ComponentType<P & InvoiceMailReceiptPreviewProps>,
) => {
return function WithInvoiceMailReceiptPreviewProps(props: P) {
const message = useSendInvoiceMailMessage();
const { invoiceMailState } = useInvoiceSendMailBoot();
const items = useMemo(
() =>
invoiceMailState?.entries?.map((entry: any) => ({
quantity: entry.quantity,
total: entry.totalFormatted,
label: entry.name,
})),
[invoiceMailState?.entries],
);
const mailReceiptPreviewProps = {
companyName: invoiceMailState?.companyName,
companyLogoUri: invoiceMailState?.companyLogoUri,
primaryColor: invoiceMailState?.primaryColor,
total: invoiceMailState?.totalFormatted,
dueDate: invoiceMailState?.dueDateFormatted,
dueAmount: invoiceMailState?.dueAmountFormatted,
invoiceNumber: invoiceMailState?.invoiceNo,
items,
message,
};
return <WrappedComponent {...mailReceiptPreviewProps} {...props} />;
};
};
export const InvoiceMailReceiptPreviewWithProps =
withInvoiceMailReceiptPreviewProps(InvoiceMailReceiptPreview);

View File

@@ -1,4 +1,3 @@
// @ts-nocheck
import React, { createContext, useContext } from 'react'; import React, { createContext, useContext } from 'react';
import { Spinner } from '@blueprintjs/core'; import { Spinner } from '@blueprintjs/core';
import { import {

View File

@@ -1,7 +1,8 @@
// @ts-nocheck
import React from 'react';
import * as R from 'ramda'; import * as R from 'ramda';
import { Drawer, DrawerSuspense } from '@/components'; import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers'; import withDrawers from '@/containers/Drawer/withDrawers';
import React from 'react';
const InvoiceSendMailContent = React.lazy(() => const InvoiceSendMailContent = React.lazy(() =>
import('./InvoiceSendMailContent').then((module) => ({ import('./InvoiceSendMailContent').then((module) => ({

View File

@@ -18,9 +18,14 @@ import {
import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useDrawerActions } from '@/hooks/state'; import { useDrawerActions } from '@/hooks/state';
import { useInvoiceMailItems, useSendInvoiceFormatArgsOptions } from './_hooks'; import { useInvoiceMailItems, useSendInvoiceFormatArgsOptions } from './_hooks';
import { InvoiceSendMailFormValues } from './_types';
// Create new account renderer. // Create new account renderer.
const createNewItemRenderer = (query, active, handleClick) => { const createNewItemRenderer = (
query: string,
active: boolean,
handleClick: React.MouseEventHandler<HTMLElement>,
) => {
return ( return (
<MenuItem <MenuItem
icon="add" icon="add"
@@ -32,7 +37,7 @@ const createNewItemRenderer = (query, active, handleClick) => {
}; };
// Create new item from the given query string. // Create new item from the given query string.
const createNewItemFromQuery = (name) => ({ name }); const createNewItemFromQuery = (text: string): SelectOptionProps => ({ text });
const styleEmailButton = css` const styleEmailButton = css`
&.bp4-button.bp4-small { &.bp4-button.bp4-small {
@@ -62,19 +67,19 @@ export function InvoiceSendMailFields() {
const [showCCField, setShowCCField] = useState<boolean>(false); const [showCCField, setShowCCField] = useState<boolean>(false);
const [showBccField, setShowBccField] = useState<boolean>(false); const [showBccField, setShowBccField] = useState<boolean>(false);
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { values, setFieldValue } =
const { values, setFieldValue } = useFormikContext(); useFormikContext<InvoiceSendMailFormValues>();
const items = useInvoiceMailItems(); const items = useInvoiceMailItems();
const argsOptions = useSendInvoiceFormatArgsOptions(); const argsOptions = useSendInvoiceFormatArgsOptions();
const handleClickCcBtn = (event) => { const handleClickCcBtn = (event: React.MouseEvent<HTMLElement>) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
setShowCCField(true); setShowCCField(true);
}; };
const handleClickBccBtn = (event) => { const handleClickBccBtn = (event: React.MouseEvent<HTMLElement>) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@@ -82,64 +87,71 @@ export function InvoiceSendMailFields() {
}; };
const handleCreateToItemSelect = (value: SelectOptionProps) => { const handleCreateToItemSelect = (value: SelectOptionProps) => {
setFieldValue('to', [...values?.to, value?.name]); setFieldValue('to', [...values?.to, value?.text]);
}; };
const handleCreateCcItemSelect = (value: SelectOptionProps) => { const handleCreateCcItemSelect = (value: SelectOptionProps) => {
setFieldValue('cc', [...values?.cc, value?.name]); setFieldValue('cc', [...values?.cc, value?.text]);
}; };
const handleCreateBccItemSelect = (value: SelectOptionProps) => { const handleCreateBccItemSelect = (value: SelectOptionProps) => {
setFieldValue('bcc', [...values?.bcc, value?.name]); setFieldValue('bcc', [...values?.bcc, value?.text]);
}; };
const rightElementsToField = useMemo(() => ( const rightElementsToField = useMemo(
<Group () => (
spacing={0} <Group
paddingRight={'7px'} spacing={0}
paddingTop={'7px'} paddingRight={'7px'}
fontWeight={500} paddingTop={'7px'}
color={'#000'} fontWeight={500}
> color={'#000'}
<Button
onClick={handleClickCcBtn}
minimal
small
className={styleEmailButton}
> >
CC <Button
</Button> onClick={handleClickCcBtn}
minimal
small
className={styleEmailButton}
>
CC
</Button>
<Button <Button
onClick={handleClickBccBtn} onClick={handleClickBccBtn}
minimal minimal
small small
className={styleEmailButton} className={styleEmailButton}
> >
BCC BCC
</Button> </Button>
</Group> </Group>
), []); ),
[],
);
const handleTextareaChange = useCallback((item: SelectOptionProps) => { const handleTextareaChange = useCallback(
const textarea = textareaRef.current; (item: SelectOptionProps) => {
if (!textarea) return; const textarea = textareaRef.current;
if (!textarea) return;
const { selectionStart, selectionEnd, value: text } = textarea; const { selectionStart, selectionEnd, value: text } = textarea;
const insertText = `{${item.value}}`; const insertText = `{${item.value}}`;
const message = const message =
text.substring(0, selectionStart) + text.substring(0, selectionStart) +
insertText + insertText +
text.substring(selectionEnd); text.substring(selectionEnd);
setFieldValue('message', message); setFieldValue('message', message);
// Move the cursor to the end of the inserted text // Move the cursor to the end of the inserted text
setTimeout(() => { setTimeout(() => {
textarea.selectionStart = textarea.selectionEnd = textarea.selectionStart = textarea.selectionEnd =
selectionStart + insertText.length; selectionStart + insertText.length;
textarea.focus(); textarea.focus();
}, 0); }, 0);
}, [setFieldValue]); },
[setFieldValue],
);
return ( return (
<Stack <Stack
@@ -233,7 +245,7 @@ export function InvoiceSendMailFields() {
position: Position.BOTTOM_LEFT, position: Position.BOTTOM_LEFT,
minimal: true, minimal: true,
}} }}
input={({ activeItem, text, label, value }) => ( input={() => (
<Button <Button
minimal minimal
rightIcon={ rightIcon={

View File

@@ -3,9 +3,9 @@ import { css } from '@emotion/css';
import { Tab, Tabs } from '@blueprintjs/core'; import { Tab, Tabs } from '@blueprintjs/core';
import { Stack } from '@/components'; import { Stack } from '@/components';
const InvoiceMailReceiptPreviewConneceted = lazy(() => const InvoiceMailReceiptPreviewConnected = lazy(() =>
import('./InvoiceMailReceiptPreviewConnected.').then((module) => ({ import('./InvoiceMailReceiptPreviewConnected').then((module) => ({
default: module.InvoiceMailReceiptPreviewConneceted, default: module.InvoiceMailReceiptPreviewConnected,
})), })),
); );
const InvoiceSendPdfPreviewConnected = lazy(() => const InvoiceSendPdfPreviewConnected = lazy(() =>
@@ -51,7 +51,7 @@ export function InvoiceSendMailPreview() {
title={'Payment page'} title={'Payment page'}
panel={ panel={
<Suspense> <Suspense>
<InvoiceMailReceiptPreviewConneceted /> <InvoiceMailReceiptPreviewConnected />
</Suspense> </Suspense>
} }
/> />

View File

@@ -372,6 +372,7 @@ export function useSendSaleInvoiceMail(
// -------------------------------------- // --------------------------------------
export interface GetSaleInvoiceDefaultOptionsResponse { export interface GetSaleInvoiceDefaultOptionsResponse {
companyName: string; companyName: string;
companyLogoUri: string;
dueDate: string; dueDate: string;
dueDateFormatted: string; dueDateFormatted: string;

26
pnpm-lock.yaml generated
View File

@@ -491,11 +491,11 @@ importers:
specifier: '*' specifier: '*'
version: link:../../shared/bigcapital-utils version: link:../../shared/bigcapital-utils
'@blueprintjs-formik/core': '@blueprintjs-formik/core':
specifier: ^0.3.6 specifier: ^0.3.7
version: 0.3.6(@babel/core@7.26.0)(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1) version: 0.3.7(@babel/core@7.26.0)(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)
'@blueprintjs-formik/datetime': '@blueprintjs-formik/datetime':
specifier: ^0.3.7 specifier: ^0.3.7
version: 0.3.7(@babel/core@7.26.0)(@blueprintjs-formik/core@0.3.6)(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1) version: 0.3.7(@babel/core@7.26.0)(@blueprintjs-formik/core@0.3.7)(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)
'@blueprintjs-formik/select': '@blueprintjs-formik/select':
specifier: ^0.3.5 specifier: ^0.3.5
version: 0.3.5(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react@18.3.1) version: 0.3.5(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react@18.3.1)
@@ -2501,7 +2501,7 @@ packages:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
'@babel/core': 7.26.0 '@babel/core': 7.26.0
'@babel/helper-plugin-utils': 7.24.5 '@babel/helper-plugin-utils': 7.25.9
'@babel/helper-skip-transparent-expression-wrappers': 7.22.5 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5
'@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0)
dev: true dev: true
@@ -3006,7 +3006,7 @@ packages:
dependencies: dependencies:
'@babel/core': 7.24.5 '@babel/core': 7.24.5
'@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0)
'@babel/helper-plugin-utils': 7.24.5 '@babel/helper-plugin-utils': 7.25.9
'@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.5) '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.5)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -3020,7 +3020,7 @@ packages:
dependencies: dependencies:
'@babel/core': 7.26.0 '@babel/core': 7.26.0
'@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0)
'@babel/helper-plugin-utils': 7.24.5 '@babel/helper-plugin-utils': 7.25.9
'@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.26.0) '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.26.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -3893,7 +3893,7 @@ packages:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
'@babel/core': 7.24.5 '@babel/core': 7.24.5
'@babel/helper-plugin-utils': 7.24.5 '@babel/helper-plugin-utils': 7.25.9
regenerator-transform: 0.15.2 regenerator-transform: 0.15.2
dev: false dev: false
@@ -3904,7 +3904,7 @@ packages:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
'@babel/core': 7.26.0 '@babel/core': 7.26.0
'@babel/helper-plugin-utils': 7.24.5 '@babel/helper-plugin-utils': 7.25.9
regenerator-transform: 0.15.2 regenerator-transform: 0.15.2
/@babel/plugin-transform-reserved-words@7.24.1(@babel/core@7.24.5): /@babel/plugin-transform-reserved-words@7.24.1(@babel/core@7.24.5):
@@ -4513,8 +4513,8 @@ packages:
/@bcoe/v8-coverage@0.2.3: /@bcoe/v8-coverage@0.2.3:
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
/@blueprintjs-formik/core@0.3.6(@babel/core@7.26.0)(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1): /@blueprintjs-formik/core@0.3.7(@babel/core@7.26.0)(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-02suh+bFr65i3BmWPGUbC8roGDtrYmm86MXKJ87ENoQZc+pN8P/fKW99ilIXfg5ZCcsIbUJQU5vb34ONoM71oQ==} resolution: {integrity: sha512-VVotc5x3Yjtm6gHHcYyHN705a9OtyfEtuJn+sdbrnB/LI6TbeibAg43EdXyKRfJThVPdQF9W5bP4WNigOuZxgA==}
peerDependencies: peerDependencies:
'@blueprintjs/core': ^3.52.0 || 4 || 5 '@blueprintjs/core': ^3.52.0 || 4 || 5
'@blueprintjs/select': ^3.18.12 || 4 || 5 '@blueprintjs/select': ^3.18.12 || 4 || 5
@@ -4536,7 +4536,7 @@ packages:
- react-is - react-is
dev: false dev: false
/@blueprintjs-formik/datetime@0.3.7(@babel/core@7.26.0)(@blueprintjs-formik/core@0.3.6)(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1): /@blueprintjs-formik/datetime@0.3.7(@babel/core@7.26.0)(@blueprintjs-formik/core@0.3.7)(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-AZCxM0at6n0kuQaufp5oMYID9tHwz5UlsSGW58j6gNdbJSwQBkeOt5DWPMj6oD7kJGaMXeKxW5/7rLahDX3BPQ==} resolution: {integrity: sha512-AZCxM0at6n0kuQaufp5oMYID9tHwz5UlsSGW58j6gNdbJSwQBkeOt5DWPMj6oD7kJGaMXeKxW5/7rLahDX3BPQ==}
peerDependencies: peerDependencies:
'@blueprintjs-formik/core': ^0.3.2 || 4 || 5 '@blueprintjs-formik/core': ^0.3.2 || 4 || 5
@@ -4546,7 +4546,7 @@ packages:
react: 16 || ^17.0.2 || 18 react: 16 || ^17.0.2 || 18
react-dom: 16 || ^17.0.2 || 18 react-dom: 16 || ^17.0.2 || 18
dependencies: dependencies:
'@blueprintjs-formik/core': 0.3.6(@babel/core@7.26.0)(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1) '@blueprintjs-formik/core': 0.3.7(@babel/core@7.26.0)(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)
'@blueprintjs/core': 4.20.2(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1) '@blueprintjs/core': 4.20.2(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@blueprintjs/select': 4.9.24(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1) '@blueprintjs/select': 4.9.24(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
formik: 2.4.6(react@18.3.1) formik: 2.4.6(react@18.3.1)
@@ -11863,7 +11863,7 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@eslint-community/regexpp': 4.10.0 '@eslint-community/regexpp': 4.11.2
'@typescript-eslint/parser': 8.11.0(eslint@9.13.0)(typescript@5.6.3) '@typescript-eslint/parser': 8.11.0(eslint@9.13.0)(typescript@5.6.3)
'@typescript-eslint/scope-manager': 8.11.0 '@typescript-eslint/scope-manager': 8.11.0
'@typescript-eslint/type-utils': 8.11.0(eslint@9.13.0)(typescript@5.6.3) '@typescript-eslint/type-utils': 8.11.0(eslint@9.13.0)(typescript@5.6.3)

View File

@@ -95,109 +95,109 @@ export const InvoicePaymentEmail: React.FC<
items, items,
}) => { }) => {
return ( return (
<Html lang="en"> <Html lang="en">
<Head /> <Head />
<Preview>{preview}</Preview> <Preview>{preview}</Preview>
<Tailwind> <Tailwind>
<Body style={bodyStyle}> <Body style={bodyStyle}>
<Container style={containerStyle}> <Container style={containerStyle}>
<Section style={mainSectionStyle}> <Section style={mainSectionStyle}>
{companyLogoUri && ( {companyLogoUri && (
<Section style={logoSectionStyle}> <Section style={logoSectionStyle}>
<div <div
style={{ style={{
...companyLogoStyle, ...companyLogoStyle,
backgroundImage: `url("${companyLogoUri}")`, backgroundImage: `url("${companyLogoUri}")`,
}} }}
> >
Image Image
</div> </div>
</Section>
)}
<Section style={headerInfoStyle}>
<Row>
<Heading style={invoiceCompanyNameStyle}>
{companyName}
</Heading>
</Row>
<Row>
<Text style={invoiceAmountStyle}>{invoiceAmount}</Text>
</Row>
<Row>
<Text style={invoiceNumberStyle}>
{invoiceNumberLabel?.replace(
'{invoiceNumber}',
invoiceNumber
)}
</Text>
</Row>
<Row>
<Text style={invoiceDateStyle}>
{dueDateLabel.replace('{dueDate}', dueDate)}
</Text>
</Row>
</Section> </Section>
)}
<Section style={headerInfoStyle}> <Text style={invoiceMessageStyle}>{invoiceMessage}</Text>
<Row> <Button
<Heading style={invoiceCompanyNameStyle}> href={viewInvoiceButtonUrl}
{companyName} style={{
</Heading> ...viewInvoiceButtonStyle,
</Row> backgroundColor: primaryColor,
<Row> }}
<Text style={invoiceAmountStyle}>{invoiceAmount}</Text> >
</Row> {viewInvoiceButtonLabel}
<Row> </Button>
<Text style={invoiceNumberStyle}>
{invoiceNumberLabel?.replace(
'{invoiceNumber}',
invoiceNumber
)}
</Text>
</Row>
<Row>
<Text style={invoiceDateStyle}>
{dueDateLabel.replace('{dueDate}', dueDate)}
</Text>
</Row>
</Section>
<Text style={invoiceMessageStyle}>{invoiceMessage}</Text> <Section style={totalsSectionStyle}>
<Button {items.map((item, index) => (
href={viewInvoiceButtonUrl} <Row key={index} style={itemLineRowStyle}>
style={{ <Column width={'50%'}>
...viewInvoiceButtonStyle, <Text style={listItemLabelStyle}>{item.label}</Text>
backgroundColor: primaryColor, </Column>
}}
>
{viewInvoiceButtonLabel}
</Button>
<Section style={totalsSectionStyle}> <Column width={'50%'}>
{items.map((item, index) => ( <Text style={listItemAmountStyle}>
<Row key={index} style={itemLineRowStyle}> {item.quantity} x {item.rate}
</Text>
</Column>
</Row>
))}
<Row style={dueAmounLineRowStyle}>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={listItemLabelStyle}>{item.label}</Text> <Text style={dueAmountLineItemLabelStyle}>
{dueAmountLabel}
</Text>
</Column> </Column>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={listItemAmountStyle}> <Text style={dueAmountLineItemAmountStyle}>
{item.quantity} x {item.rate} {dueAmount}
</Text> </Text>
</Column> </Column>
</Row> </Row>
))}
<Row style={dueAmounLineRowStyle}> <Row style={totalLineRowStyle}>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={dueAmountLineItemLabelStyle}> <Text style={totalLineItemLabelStyle}>{totalLabel}</Text>
{dueAmountLabel} </Column>
</Text>
</Column>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={dueAmountLineItemAmountStyle}> <Text style={totalLineItemAmountStyle}>{total}</Text>
{dueAmount} </Column>
</Text> </Row>
</Column> </Section>
</Row>
<Row style={totalLineRowStyle}>
<Column width={'50%'}>
<Text style={totalLineItemLabelStyle}>{totalLabel}</Text>
</Column>
<Column width={'50%'}>
<Text style={totalLineItemAmountStyle}>{total}</Text>
</Column>
</Row>
</Section> </Section>
</Section> </Container>
</Container> </Body>
</Body> </Tailwind>
</Tailwind> </Html>
</Html> );
); };
};
export const renderInvoicePaymentEmail = (props: InvoicePaymentEmailProps) => { export const renderInvoicePaymentEmail = (props: InvoicePaymentEmailProps) => {
return render(<InvoicePaymentEmail {...props} />); return render(<InvoicePaymentEmail {...props} />);
@@ -274,6 +274,7 @@ const invoiceMessageStyle: CSSProperties = {
whiteSpace: 'pre-line', whiteSpace: 'pre-line',
color: '#252A31', color: '#252A31',
margin: '0 0 20px 0', margin: '0 0 20px 0',
lineHeight: '20px',
}; };
const dueAmounLineRowStyle: CSSProperties = { const dueAmounLineRowStyle: CSSProperties = {