feat: wip email templates

This commit is contained in:
Ahmed Bouhuolia
2024-11-19 11:56:52 +02:00
parent 7e65f3f642
commit 2c54092591
12 changed files with 626 additions and 124 deletions

View File

@@ -0,0 +1,21 @@
import { Meta, StoryFn } from '@storybook/react';
import {
CreditNoteEmailProps,
CreditNoteEmailTemplate,
} from './CreditNoteEmailTemplate';
export default {
title: 'Email/CreditNoteEmailTemplate',
component: CreditNoteEmailTemplate,
} as Meta;
const Template: StoryFn<CreditNoteEmailProps> = (args) => (
<CreditNoteEmailTemplate {...args} />
);
export const Default: StoryFn<CreditNoteEmailProps> = Template.bind({});
Default.args = {
total: '$1,000.00',
items: [{ label: 'Swaniawski Muller', quantity: '1', rate: '$1,000.00' }],
};

View File

@@ -0,0 +1,40 @@
export interface CreditNoteEmailProps {
preview?: string;
companyName?: string;
companyLogoUri: string;
primaryColor?: string;
total: string;
totalLabel?: string;
// # Items
items: Array<{ label: string; quantity: string; rate: string }>;
viewEstimateButtonLabel?: string;
viewEstimateButtonUrl?: string;
}
export const CreditNoteEmailTemplate: React.FC<
Readonly<CreditNoteEmailProps>
> = ({
preview,
// # Company
companyName,
companyLogoUri,
// # Colors
primaryColor = 'rgb(0, 82, 204)',
// # invoice total
total,
totalLabel = 'Total',
// # View invoice button
viewEstimateButtonLabel = 'View Estimate',
viewEstimateButtonUrl,
}) => {
return <h1>asdasd</h1>;
};

View File

@@ -0,0 +1,29 @@
import { Html, Head, Body, Preview, Tailwind } from '@react-email/components';
import { CSSProperties } from 'react';
interface EmailTemplateLayoutProps {
children?: React.ReactNode;
preview: string;
}
export const EmailTemplateLayout = ({
children,
preview,
}: EmailTemplateLayoutProps) => {
return (
<Html lang="en">
<Head />
<Preview>{preview}</Preview>
<Tailwind>
<Body style={bodyStyle}>{children}</Body>
</Tailwind>
</Html>
);
};
const bodyStyle: CSSProperties = {
backgroundColor: '#F5F5F5',
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
padding: '40px 0',
};

View File

@@ -0,0 +1,33 @@
import { Meta, StoryFn } from '@storybook/react';
import {
EstimatePaymentEmailProps,
EstimatePaymentEmail,
} from './EstimatePaymentEmail';
export default {
title: 'Email/EstimatePaymentEmail',
component: EstimatePaymentEmail,
} as Meta;
const Template: StoryFn<EstimatePaymentEmailProps> = (args) => (
<EstimatePaymentEmail {...args} />
);
export const Default: StoryFn<EstimatePaymentEmailProps> = Template.bind({});
Default.args = {
total: '$1,000.00',
items: [{ label: 'Swaniawski Muller', quantity: '1', rate: '$1,000.00' }],
message: `Hi Ahmed Bouhuolia,
Here's invoice # INV-00005 for $1,000.00
The amount outstanding of $1,000.00 is due on 10 Oct 2024.
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,
Bigcapital`,
};

View File

@@ -0,0 +1,259 @@
import { CSSProperties } from 'react';
import {
Button,
Column,
Container,
Heading,
Row,
Section,
Text,
} from '@react-email/components';
import { EmailTemplateLayout } from './EmailTemplateLayout';
export interface EstimatePaymentEmailProps {
preview: string;
// # Company
companyName?: string;
companyLogoUri: string;
// # Colors
primaryColor?: string;
// # Total
total: string;
totalLabel?: string;
// # Estimate No#
estimateNumber?: string;
estimateNumberLabel?: string;
// # Expiration date.
expirationDateLabel?: string;
expirationDate?: string;
// # Items
items: Array<{ label: string; quantity: string; rate: string }>;
// # View estimate button.
viewEstimateButtonLabel?: string;
viewEstimateButtonUrl?: string;
// # Estimate message
message?: string;
}
export const EstimatePaymentEmail: React.FC<
Readonly<EstimatePaymentEmailProps>
> = ({
preview,
// # Company
companyName,
companyLogoUri,
// # Colors
primaryColor = 'rgb(0, 82, 204)',
// # invoice total
total,
totalLabel = 'Total',
// # Estimate No#
estimateNumberLabel = 'Estimate No: {estimateNumber}',
estimateNumber = 'EST-00001',
// # Expiration date
expirationDateLabel = 'Expiration Date: {expirationDate}',
expirationDate = '12/12/2021',
// # View invoice button
viewEstimateButtonLabel = 'View Estimate',
viewEstimateButtonUrl,
// # Message
message = '',
// # Items
items = [],
}) => {
return (
<EmailTemplateLayout preview={preview}>
<Container style={containerStyle}>
{companyLogoUri && (
<Section style={logoSectionStyle}>
<div
style={{
...companyLogoStyle,
backgroundImage: `url("${companyLogoUri}")`,
}}
></div>
</Section>
)}
<Section style={headerInfoStyle}>
<Row>
<Heading style={invoiceCompanyNameStyle}>{companyName}</Heading>
</Row>
<Row>
<Text style={estimateAmountStyle}>{total}</Text>
</Row>
<Row>
<Text style={estimateNumberStyle}>
{estimateNumberLabel?.replace('{estimateNumber}', estimateNumber)}
</Text>
</Row>
<Row>
<Text style={estimateExpirationStyle}>
{expirationDateLabel.replace('{expirationDate}', expirationDate)}
</Text>
</Row>
</Section>
<Text style={estimateMessageStyle}>{message}</Text>
<Button
href={viewEstimateButtonUrl}
style={{
...viewEstimateButtonStyle,
backgroundColor: primaryColor,
}}
>
{viewEstimateButtonLabel}
</Button>
<Section style={totalsSectionStyle}>
{items.map((item, index) => (
<Row key={index} style={itemLineRowStyle}>
<Column width={'50%'}>
<Text style={listItemLabelStyle}>{item.label}</Text>
</Column>
<Column width={'50%'}>
<Text style={listItemAmountStyle}>
{item.quantity} x {item.rate}
</Text>
</Column>
</Row>
))}
<Row style={totalLineRowStyle}>
<Column width={'50%'}>
<Text style={totalLineItemLabelStyle}>{totalLabel}</Text>
</Column>
<Column width={'50%'}>
<Text style={totalLineItemAmountStyle}>{total}</Text>
</Column>
</Row>
</Section>
</Container>
</EmailTemplateLayout>
);
};
const containerStyle: CSSProperties = {
backgroundColor: '#fff',
width: '100%',
maxWidth: '500px',
padding: '35px 25px',
color: '#000',
borderRadius: '5px',
};
const headerInfoStyle: CSSProperties = {
textAlign: 'center',
marginBottom: 20,
};
const estimateAmountStyle: CSSProperties = {
margin: 0,
color: '#383E47',
fontWeight: 500,
};
const estimateNumberStyle: CSSProperties = {
margin: 0,
fontSize: '13px',
color: '#404854',
};
const estimateExpirationStyle: CSSProperties = {
margin: 0,
fontSize: '13px',
color: '#404854',
};
const invoiceCompanyNameStyle: CSSProperties = {
margin: 0,
fontSize: '18px',
fontWeight: 500,
color: '#404854',
};
const viewEstimateButtonStyle: CSSProperties = {
display: 'block',
cursor: 'pointer',
textAlign: 'center',
fontSize: 16,
padding: '10px 15px',
lineHeight: '1',
backgroundColor: 'rgb(0, 82, 204)',
color: '#fff',
borderRadius: '5px',
};
const listItemLabelStyle: CSSProperties = {
margin: 0,
};
const listItemAmountStyle: CSSProperties = {
margin: 0,
textAlign: 'right',
};
const estimateMessageStyle: CSSProperties = {
whiteSpace: 'pre-line',
color: '#252A31',
margin: '0 0 20px 0',
lineHeight: '20px',
};
const totalLineRowStyle: CSSProperties = {
borderBottom: '1px solid #000',
height: 40,
};
const totalLineItemLabelStyle: CSSProperties = {
...listItemLabelStyle,
fontWeight: 500,
};
const totalLineItemAmountStyle: CSSProperties = {
...listItemAmountStyle,
fontWeight: 600,
};
const itemLineRowStyle: CSSProperties = {
borderBottom: '1px solid #D9D9D9',
height: 40,
};
const totalsSectionStyle = {
marginTop: '20px',
borderTop: '1px solid #D9D9D9',
};
const logoSectionStyle = {
marginBottom: '15px',
};
const companyLogoStyle = {
height: 90,
width: 90,
borderRadius: '3px',
marginLeft: 'auto',
marginRight: 'auto',
textIndent: '-999999px',
overflow: 'hidden',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center center',
backgroundSize: 'contain',
};

View File

@@ -1,18 +1,38 @@
import { Meta } from '@storybook/react';
import { StoryFn } from '@storybook/react';
import { Meta, StoryFn } from '@storybook/react';
import {
InvoicePaymentEmail,
InvoicePaymentEmailProps,
} from './InvoicePaymentEmail';
const meta: Meta<typeof InvoicePaymentEmail> = {
title: 'Invoice Payment Email',
export default {
title: 'Email/InvoicePaymentEmail',
component: InvoicePaymentEmail,
} as Meta;
const Template: StoryFn<InvoicePaymentEmailProps> = (args) => (
<InvoicePaymentEmail {...args} />
);
export const Default: StoryFn<InvoicePaymentEmailProps> = Template.bind({});
Default.args = {
// Add default props here
invoiceNumber: 'INV-12345',
companyName: 'Bigcapital, Inc.',
invoiceMessage: `Hi Ahmed Bouhuolia,
Here's invoice # INV-00005 for $1,000.00
The amount outstanding of $1,000.00 is due on 10 Oct 2024.
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,
Bigcapital`,
dueDate: ' 10 Oct 2024',
total: '$1,000.00',
dueAmount: '$1,000.00',
items: [{ label: 'Swaniawski Muller', quantity: '1', rate: '$1,000.00' }],
};
export default meta;
const Template: StoryFn<typeof InvoicePaymentEmail> = (
args: InvoicePaymentEmailProps
) => <InvoicePaymentEmail {...args} />;

View File

@@ -14,6 +14,7 @@ import {
render,
} from '@react-email/components';
import { CSSProperties } from 'react';
import { EmailTemplateLayout } from './EmailTemplateLayout';
export interface InvoicePaymentEmailProps {
preview: string;
@@ -96,106 +97,90 @@ export const InvoicePaymentEmail: React.FC<
items,
}) => {
return (
<Html lang="en">
<Head />
<Preview>{preview}</Preview>
<Tailwind>
<Body style={bodyStyle}>
<Container style={containerStyle}>
<Section style={mainSectionStyle}>
{companyLogoUri && (
<Section style={logoSectionStyle}>
<div
style={{
...companyLogoStyle,
backgroundImage: `url("${companyLogoUri}")`,
}}
>
Image
</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>
<Text style={invoiceMessageStyle}>{invoiceMessage}</Text>
<Button
href={viewInvoiceButtonUrl}
<EmailTemplateLayout preview={preview}>
<Container style={containerStyle}>
<Section style={mainSectionStyle}>
{companyLogoUri && (
<Section style={logoSectionStyle}>
<div
style={{
...viewInvoiceButtonStyle,
backgroundColor: primaryColor,
...companyLogoStyle,
backgroundImage: `url("${companyLogoUri}")`,
}}
>
{viewInvoiceButtonLabel}
</Button>
<Section style={totalsSectionStyle}>
{items.map((item, index) => (
<Row key={index} style={itemLineRowStyle}>
<Column width={'50%'}>
<Text style={listItemLabelStyle}>{item.label}</Text>
</Column>
<Column width={'50%'}>
<Text style={listItemAmountStyle}>
{item.quantity} x {item.rate}
</Text>
</Column>
</Row>
))}
<Row style={dueAmounLineRowStyle}>
<Column width={'50%'}>
<Text style={dueAmountLineItemLabelStyle}>
{dueAmountLabel}
</Text>
</Column>
<Column width={'50%'}>
<Text style={dueAmountLineItemAmountStyle}>
{dueAmount}
</Text>
</Column>
</Row>
<Row style={totalLineRowStyle}>
<Column width={'50%'}>
<Text style={totalLineItemLabelStyle}>{totalLabel}</Text>
</Column>
<Column width={'50%'}>
<Text style={totalLineItemAmountStyle}>{total}</Text>
</Column>
</Row>
</Section>
></div>
</Section>
</Container>
</Body>
</Tailwind>
</Html>
)}
<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>
<Text style={invoiceMessageStyle}>{invoiceMessage}</Text>
<Button
href={viewInvoiceButtonUrl}
style={{
...viewInvoiceButtonStyle,
backgroundColor: primaryColor,
}}
>
{viewInvoiceButtonLabel}
</Button>
<Section style={totalsSectionStyle}>
{items.map((item, index) => (
<Row key={index} style={itemLineRowStyle}>
<Column width={'50%'}>
<Text style={listItemLabelStyle}>{item.label}</Text>
</Column>
<Column width={'50%'}>
<Text style={listItemAmountStyle}>
{item.quantity} x {item.rate}
</Text>
</Column>
</Row>
))}
<Row style={dueAmounLineRowStyle}>
<Column width={'50%'}>
<Text style={dueAmountLineItemLabelStyle}>
{dueAmountLabel}
</Text>
</Column>
<Column width={'50%'}>
<Text style={dueAmountLineItemAmountStyle}>{dueAmount}</Text>
</Column>
</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>
</Container>
</EmailTemplateLayout>
);
};
@@ -203,14 +188,6 @@ export const renderInvoicePaymentEmail = (props: InvoicePaymentEmailProps) => {
return render(<InvoicePaymentEmail {...props} />);
};
const bodyStyle: CSSProperties = {
backgroundColor: '#F5F5F5',
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
padding: '40px 0',
};
const containerStyle: CSSProperties = {
backgroundColor: '#fff',
width: '100%',

View File

@@ -0,0 +1,19 @@
import { Meta, StoryFn } from '@storybook/react';
import {
PaymentReceivedEmailTemplateProps,
PaymentReceivedEmailTemplate,
} from './PaymentReceivedEmailTemplate';
export default {
title: 'Email/PaymentReceivedEmailTemplate',
component: PaymentReceivedEmailTemplate,
} as Meta;
const Template: StoryFn<PaymentReceivedEmailTemplateProps> = (args) => (
<PaymentReceivedEmailTemplate {...args} />
);
export const Default: StoryFn<PaymentReceivedEmailTemplateProps> =
Template.bind({});
Default.args = {};

View File

@@ -0,0 +1,40 @@
export interface PaymentReceivedEmailTemplateProps {
preview?: string;
companyName?: string;
companyLogoUri: string;
primaryColor?: string;
total: string;
totalLabel?: string;
// # Items
items: Array<{ label: string; quantity: string; rate: string }>;
viewEstimateButtonLabel?: string;
viewEstimateButtonUrl?: string;
}
export const PaymentReceivedEmailTemplate: React.FC<
Readonly<PaymentReceivedEmailTemplateProps>
> = ({
preview,
// # Company
companyName,
companyLogoUri,
// # Colors
primaryColor = 'rgb(0, 82, 204)',
// # invoice total
total,
totalLabel = 'Total',
// # View invoice button
viewEstimateButtonLabel = 'View Estimate',
viewEstimateButtonUrl,
}) => {
return <h1>asdasd</h1>;
};

View File

@@ -0,0 +1,20 @@
import { Meta, StoryFn } from '@storybook/react';
import {
ReceiptEmailTemplate,
ReceiptEmailTemplateProps,
} from './ReceiptPaymentEmail';
export default {
title: 'Email/ReceiptPaymentEmail',
component: ReceiptEmailTemplate,
} as Meta;
const Template: StoryFn<ReceiptEmailTemplateProps> = (args) => (
<ReceiptEmailTemplate {...args} />
);
export const Default: StoryFn<ReceiptEmailTemplateProps> = Template.bind({});
Default.args = {
// Add default props here
};

View File

@@ -0,0 +1,45 @@
export interface ReceiptEmailTemplateProps {
preview?: string;
// # Company
companyName?: string;
companyLogoUri: string;
// # Colors
primaryColor?: string;
// # Invoice total
total: string;
totalLabel?: string;
// # Items
items: Array<{ label: string; quantity: string; rate: string }>;
viewReceiptButtonLabel?: string;
viewReceiptButtonUrl?: string;
}
export const ReceiptEmailTemplate: React.FC<
Readonly<ReceiptEmailTemplateProps>
> = ({
preview,
// # Company
companyName,
companyLogoUri,
// # Colors
primaryColor = 'rgb(0, 82, 204)',
// # Invoice total
total,
totalLabel = 'Total',
// # View invoice button
viewReceiptButtonLabel = 'View Estimate',
viewReceiptButtonUrl,
}) => {
return (
<h1>asdasd</h1>
)
};