feat: wip invoice mail receipt preview

This commit is contained in:
Ahmed Bouhuolia
2024-10-22 11:59:15 +02:00
parent dffd818396
commit b7f316d25a
9 changed files with 290 additions and 98 deletions

View File

@@ -1,6 +1,7 @@
import { InvoicePaymentPage, PaymentPageProps } from './PaymentPage';
interface InvoicePaymentPagePreviewProps extends Partial<PaymentPageProps> { }
export interface InvoicePaymentPagePreviewProps
extends Partial<PaymentPageProps> { }
export function InvoicePaymentPagePreview(
props: InvoicePaymentPagePreviewProps,

View File

@@ -2,58 +2,117 @@ import { Text, Classes, Button, Intent, ButtonProps } from '@blueprintjs/core';
import clsx from 'classnames';
import { Box, Group, Stack } from '@/components';
import styles from './PaymentPortal.module.scss';
import { css } from '@emotion/css';
export interface PaymentPageProps {
// # Company name
companyLogoUri: string;
organizationName: string;
organizationAddress: string;
// # Colors
primaryColor?: string;
// # Customer name
customerName: string;
subtotal: string;
total: string;
dueDate: string;
viewInvoiceLabel?: string;
invoiceNumber: string;
totalLabel?: string;
subtotalLabel?: string;
customerAddress?: string;
downloadInvoiceBtnLabel?: string;
showPayButton?: boolean;
// # Subtotal
subtotal: string;
subtotalLabel?: string;
// # Total
total: string;
totalLabel?: string;
// # Due date
dueDate: string;
// # Paid amount
paidAmount: string;
paidAmountLabel?: string;
organizationAddress: string;
// # Due amount
dueAmount: string;
dueAmountLabel?: string;
// # Download invoice button
downloadInvoiceBtnLabel?: string;
downloadInvoiceButtonProps?: Partial<ButtonProps>;
payInvoiceButtonProps?: Partial<ButtonProps>;
// # View invoice button
viewInvoiceLabel?: string;
viewInvoiceButtonProps?: Partial<ButtonProps>;
// # Invoice number
invoiceNumber: string;
invoiceNumberLabel?: string;
// # Pay button
showPayButton?: boolean;
payButtonLabel?: string;
payInvoiceButtonProps?: Partial<ButtonProps>;
// # Buy note
buyNote?: string;
// # Copyright
copyrightText?: string;
}
export function InvoicePaymentPage({
// # Company
companyLogoUri,
organizationName,
organizationAddress,
// # Colors
primaryColor,
// # Customer
customerName,
customerAddress,
// # Subtotal
subtotal,
subtotalLabel = 'Subtotal',
// # Total
total,
totalLabel = 'Total',
// # Due date
dueDate,
// # Paid amount
paidAmount,
paidAmountLabel = 'Paid Amount (-)',
// # Invoice number
invoiceNumber,
customerAddress,
totalLabel = 'Total',
subtotalLabel = 'Subtotal',
viewInvoiceLabel = 'View Invoice',
invoiceNumberLabel = 'Invoice #',
// # Download invoice button
downloadInvoiceBtnLabel = 'Download Invoice',
showPayButton = true,
organizationAddress,
downloadInvoiceButtonProps,
// # View invoice button
viewInvoiceLabel = 'View Invoice',
viewInvoiceButtonProps,
// # Due amount
dueAmount,
dueAmountLabel = 'Due Amount',
downloadInvoiceButtonProps,
// # Pay button
showPayButton = true,
payButtonLabel = 'Pay {total}',
payInvoiceButtonProps,
viewInvoiceButtonProps,
invoiceNumberLabel = 'Invoice #',
// # Buy note
buyNote = 'By confirming your payment, you allow Bigcapital Technology, Inc. to charge you for this payment and save your payment information in accordance with their terms.',
// # Copyright
copyrightText = `© 2024 Bigcapital Technology, Inc. <br /> All rights reserved.`,
}: PaymentPageProps) {
return (
@@ -91,7 +150,9 @@ export function InvoicePaymentPage({
)}
</Stack>
<h2 className={styles.invoiceNumber}>Invoice {invoiceNumber}</h2>
<h2 className={styles.invoiceNumber}>
{invoiceNumberLabel} {invoiceNumber}
</h2>
<Stack spacing={0} className={styles.totals}>
<Group
@@ -125,7 +186,7 @@ export function InvoicePaymentPage({
position={'apart'}
className={clsx(styles.totalItem, styles.borderBottomDark)}
>
<Text>Due Amount</Text>
<Text>{dueAmountLabel}</Text>
<Text style={{ fontWeight: 500 }}>{dueAmount}</Text>
</Group>
</Stack>
@@ -150,10 +211,18 @@ export function InvoicePaymentPage({
{showPayButton && (
<Button
intent={Intent.PRIMARY}
className={clsx(styles.footerButton, styles.buyButton)}
className={clsx(
styles.footerButton,
styles.buyButton,
css`
&.bp4-intent-primary {
background-color: ${primaryColor};
}
`,
)}
{...payInvoiceButtonProps}
>
Pay {total}
{payButtonLabel.replace('{total}', total)}
</Button>
)}
</Stack>

View File

@@ -28,6 +28,7 @@
font-weight: 500;
color: #222;
font-size: 26px;
line-height: 1.35;
}
.invoiceDueDate{

View File

@@ -1,11 +1,5 @@
import React, { lazy, Suspense } from 'react';
import * as R from 'ramda';
import { useFormikContext } from 'formik';
import { lazy, Suspense } from 'react';
import { Spinner, Tab } from '@blueprintjs/core';
import {
InvoicePaperTemplate,
InvoicePaperTemplateProps,
} from './InvoicePaperTemplate';
import {
ElementCustomize,
ElementCustomizeContent,
@@ -17,21 +11,26 @@ import { InvoiceCustomizeSchema } from './InvoiceCustomizeForm.schema';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useDrawerActions } from '@/hooks/state';
import { BrandingTemplateForm } from '@/containers/BrandingTemplates/BrandingTemplateForm';
import { useElementCustomizeContext } from '@/containers/ElementCustomize/ElementCustomizeProvider';
import { initialValues } from './constants';
import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils';
import { InvoiceCustomizeTabs } from './InvoiceCustomizeTabs';
const InvoicePaymentPagePreview = lazy(() =>
import('@/containers/PaymentPortal/InvoicePaymentPagePreview').then(
(module) => ({ default: module.InvoicePaymentPagePreview }),
),
const InvoiceCustomizePaymentPreview = lazy(() =>
import('./InvoiceCustomizePaymentPreview').then((module) => ({
default: module.InvoiceCustomizePaymentPreview,
})),
);
const InvoiceMailReceiptPreview = lazy(() =>
import(
'@/containers/Sales/Invoices/InvoiceCustomize/InvoiceMailReceiptPreview'
).then((module) => ({ default: module.InvoiceMailReceiptPreview })),
const InvoiceCustomizeMailReceiptPreview = lazy(() =>
import('./InvoiceCustomizeMailReceiptPreview').then((module) => ({
default: module.InvoiceCustomizeMailReceiptPreview,
})),
);
const InvoiceCustomizePdfPreview = lazy(() =>
import('./InvoiceCustomizePdfPreview').then((module) => ({
default: module.InvoiceCustomizePdfPreview,
})),
);
/**
@@ -80,7 +79,7 @@ function InvoiceCustomizeFormContent() {
title={'PDF document'}
panel={
<Suspense fallback={<Spinner />}>
<InvoicePaperTemplateFormConnected />
<InvoiceCustomizePdfPreview />
</Suspense>
}
/>
@@ -89,7 +88,7 @@ function InvoiceCustomizeFormContent() {
title={'Payment page'}
panel={
<Suspense fallback={<Spinner />}>
<InvoicePaymentPagePreview />
<InvoiceCustomizePaymentPreview />
</Suspense>
}
/>
@@ -98,7 +97,7 @@ function InvoiceCustomizeFormContent() {
title={'Email receipt'}
panel={
<Suspense fallback={<Spinner />}>
<InvoiceMailReceiptPreview mx={'auto'} />
<InvoiceCustomizeMailReceiptPreview mx={'auto'} />
</Suspense>
}
/>
@@ -119,27 +118,3 @@ function InvoiceCustomizeFormContent() {
</ElementCustomizeContent>
);
}
/**
* Injects the `InvoicePaperTemplate` component props from the form and branding states.
* @param Component
* @returns {JSX.Element}
*/
const withInvoicePreviewTemplateProps = <P extends object>(
Component: React.ComponentType<P>,
) => {
return (props: Omit<P, keyof InvoicePaperTemplateProps>) => {
const { values } = useFormikContext<InvoiceCustomizeFormValues>();
const { brandingState } = useElementCustomizeContext();
const mergedProps: InvoicePaperTemplateProps = {
...brandingState,
...values,
};
return <Component {...(props as P)} {...mergedProps} />;
};
};
export const InvoicePaperTemplateFormConnected = R.compose(
withInvoicePreviewTemplateProps,
)(InvoicePaperTemplate);

View File

@@ -1,5 +1,6 @@
// @ts-nocheck
import { Classes, Text } from '@blueprintjs/core';
import { Link } from 'react-router-dom';
import {
FFormGroup,
FieldRequiredHint,
@@ -13,7 +14,6 @@ import { CreditCardIcon } from '@/icons/CreditCardIcon';
import { Overlay } from './Overlay';
import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils';
import { BrandingCompanyLogoUploadField } from '@/containers/ElementCustomize/components/BrandingCompanyLogoUploadField';
import { Link } from 'react-router-dom';
import { MANAGE_LINK_URL } from './constants';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useDrawerActions } from '@/hooks/state';

View File

@@ -0,0 +1,30 @@
import * as R from 'ramda';
import { InvoicePaymentPagePreviewProps } from '@/containers/PaymentPortal/InvoicePaymentPagePreview';
import { InvoiceCustomizeFormValues } from './types';
import { useElementCustomizeContext } from '@/containers/ElementCustomize/ElementCustomizeProvider';
import { useFormikContext } from 'formik';
import { InvoiceMailReceiptPreview } from './InvoiceMailReceiptPreview';
const withInvoiceMailReceiptPreviewConnected = <P extends Object>(
Component: React.ComponentType<P>,
) => {
return (props: Omit<P, keyof InvoicePaymentPagePreviewProps>) => {
const { values } = useFormikContext<InvoiceCustomizeFormValues>();
const { brandingState } = useElementCustomizeContext();
const mergedBrandingState = {
...brandingState,
...values,
};
const mergedProps: InvoicePaymentPagePreviewProps = {
companyLogoUri: mergedBrandingState?.companyLogoUri,
primaryColor: mergedBrandingState?.primaryColor,
// organizationAddress: mergedBrandingState,
};
return <Component {...(props as P)} {...mergedProps} />;
};
};
export const InvoiceCustomizeMailReceiptPreview = R.compose(
withInvoiceMailReceiptPreviewConnected,
)(InvoiceMailReceiptPreview);

View File

@@ -0,0 +1,31 @@
import * as R from 'ramda';
import { useFormikContext } from 'formik';
import {
InvoicePaymentPagePreview,
InvoicePaymentPagePreviewProps,
} from '@/containers/PaymentPortal/InvoicePaymentPagePreview';
import { useElementCustomizeContext } from '@/containers/ElementCustomize/ElementCustomizeProvider';
import { InvoiceCustomizeFormValues } from './types';
const withInvoicePaymentPreviewPageProps = <P extends Object>(
Component: React.ComponentType<P>,
) => {
return (props: Omit<P, keyof InvoicePaymentPagePreviewProps>) => {
const { values } = useFormikContext<InvoiceCustomizeFormValues>();
const { brandingState } = useElementCustomizeContext();
const mergedBrandingState = {
...brandingState,
...values,
};
const mergedProps: InvoicePaymentPagePreviewProps = {
companyLogoUri: mergedBrandingState?.companyLogoUri,
primaryColor: mergedBrandingState?.primaryColor,
};
return <Component {...(props as P)} {...mergedProps} />;
};
};
export const InvoiceCustomizePaymentPreview = R.compose(
withInvoicePaymentPreviewPageProps,
)(InvoicePaymentPagePreview);

View File

@@ -0,0 +1,32 @@
import * as R from 'ramda';
import { useFormikContext } from 'formik';
import {
InvoicePaperTemplate,
InvoicePaperTemplateProps,
} from './InvoicePaperTemplate';
import { useElementCustomizeContext } from '@/containers/ElementCustomize/ElementCustomizeProvider';
import { InvoiceCustomizeFormValues } from './types';
/**
* Injects the `InvoicePaperTemplate` component props from the form and branding states.
* @param Component
* @returns {JSX.Element}
*/
const withInvoicePreviewTemplateProps = <P extends object>(
Component: React.ComponentType<P>,
) => {
return (props: Omit<P, keyof InvoicePaperTemplateProps>) => {
const { values } = useFormikContext<InvoiceCustomizeFormValues>();
const { brandingState } = useElementCustomizeContext();
const mergedProps: InvoicePaperTemplateProps = {
...brandingState,
...values,
};
return <Component {...(props as P)} {...mergedProps} />;
};
};
export const InvoiceCustomizePdfPreview = R.compose(
withInvoicePreviewTemplateProps,
)(InvoicePaperTemplate);

View File

@@ -4,35 +4,75 @@ import { x } from '@xstyled/emotion';
import { Group, Stack, StackProps } from '@/components';
export interface InvoiceMailReceiptProps extends StackProps {
companyLogoUri?: string;
message: string;
// # Company
companyName: string;
invoiceNumber: string;
companyLogoUri?: string;
// # Colors
primaryColor?: string,
// # Due date
dueDate: string;
items?: Array<{ label: string; total: string; quantity: string | number }>;
total: string;
dueAmount: string;
totalLabel?: string;
dueDateLabel?: string;
// # Due amount
dueAmountLabel?: string;
dueAmount: string;
// # Total
total: string;
totalLabel?: string;
// # Invoice number
invoiceNumber: string;
invoiceNumberLabel?: string;
// # Mail message
message: string;
// # Invoice items
items?: Array<{ label: string; total: string; quantity: string | number }>;
// # View invoice button
showViewInvoiceButton?: boolean;
viewInvoiceButtonLabel?: string;
viewInvoiceButtonOnClick?: () => void;
invoiceNumberLabel?: string;
}
export function InvoiceMailReceipt({
companyLogoUri,
message,
// Company
companyName,
total,
invoiceNumber,
companyLogoUri,
// # Colors
primaryColor,
// Due date
dueDate,
dueDateLabel = 'Due',
// Due amount
dueAmountLabel = 'Due Amount',
dueAmount,
// Total
total,
totalLabel = 'Total',
// Invoice number
invoiceNumber,
invoiceNumberLabel = 'Invoice #',
// Invoice message
message,
// Invoice items
items,
// View invoice button
showViewInvoiceButton = true,
viewInvoiceButtonLabel = 'View Invoice',
viewInvoiceButtonOnClick,
totalLabel = 'Total',
dueAmountLabel = 'Due Amount',
invoiceNumberLabel = 'Invoice #',
...restProps
}: InvoiceMailReceiptProps) {
return (
@@ -48,7 +88,15 @@ export function InvoiceMailReceipt({
>
<Stack spacing={16} textAlign={'center'}>
{companyLogoUri && (
<x.div h={'90px'} w={'90px'} bg="#F2F2F2" mx="auto"></x.div>
<x.div
h="90px"
w="90px"
mx="auto"
backgroundRepeat="no-repeat"
backgroundPosition="center center"
backgroundSize="contain"
backgroundImage={`url("${companyLogoUri}")`}
></x.div>
)}
<Stack spacing={8}>
<x.h1 m={0} fontSize={'18px'} fontWeight={500} color="#404854">
@@ -64,7 +112,7 @@ export function InvoiceMailReceipt({
</x.span>
<x.span fontSize={'13px'} color="#404854">
Due {dueDate}
{dueDateLabel} {dueDate}
</x.span>
</Stack>
</Stack>
@@ -73,18 +121,23 @@ export function InvoiceMailReceipt({
{message}
</x.p>
<Button
large
intent={Intent.PRIMARY}
className={css`
&.bp4-large {
min-height: 38px;
}
`}
onClick={viewInvoiceButtonOnClick}
>
{viewInvoiceButtonLabel}
</Button>
{showViewInvoiceButton && (
<Button
large
intent={Intent.PRIMARY}
className={css`
&.bp4-intent-primary{
background-color: ${primaryColor};
}
&.bp4-large {
min-height: 38px;
}
`}
onClick={viewInvoiceButtonOnClick}
>
{viewInvoiceButtonLabel}
</Button>
)}
<Stack spacing={0}>
{items?.map((item, key) => (