Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions packages/internal/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ USAGE
- [`saas emails build`](#saas-emails-build)
- [`saas emails secrets`](#saas-emails-secrets)
- [`saas emails test`](#saas-emails-test)
- [`saas emails preview`](#saas-emails-preview)
- [`saas help [COMMANDS]`](#saas-help-commands)
- [`saas infra bootstrap`](#saas-infra-bootstrap)
- [`saas infra deploy [STACKNAME]`](#saas-infra-deploy-stackname)
Expand Down Expand Up @@ -640,6 +641,23 @@ EXAMPLES

_See code: [dist/commands/emails/test.js](https://github.com/apptension/saas-boilerplate/blob/v2.0.3/dist/commands/emails/test.js)_

## `saas emails preview`

Runs emails preview. To be able to see assets, please insert them inside of `webapp-emails/src/preview/static` folder.

```
USAGE
$ saas emails preview

DESCRIPTION
Runs emails preview. To be able to see assets, please insert them inside of `webapp-emails/src/preview/static` folder.

EXAMPLES
$ saas emails preview
```

_See code: [dist/commands/emails/preview.js](https://github.com/apptension/saas-boilerplate/blob/v2.0.3/dist/commands/emails/preview.js)_

## `saas help [COMMANDS]`

Display help for saas.
Expand Down
15 changes: 15 additions & 0 deletions packages/internal/cli/src/commands/emails/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { initConfig } from '../../config/init';
import { runCommand } from '../../lib/runCommand';
import { BaseCommand } from '../../baseCommand';

export default class EmailsPreview extends BaseCommand<typeof EmailsPreview> {
static description = 'Runs emails preview';

static examples = [`$ <%= config.bin %> <%= command.id %>`];

async run(): Promise<void> {
await initConfig(this, {});

await runCommand('pnpm', ['nx', 'run', 'webapp-emails:preview']);
}
}
12 changes: 6 additions & 6 deletions packages/internal/docs/docs/introduction/features/emails.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ SaaS Boilerplate offers an email system functionality out of the box, providing
engage users through email. This functionality covers everything from creating and sending email templates to scheduling
emails and sending test emails for debugging purposes.

### Lively email templates in React using Storybooks
### Lively email templates in React using React.Email

The email system functionality allows developers to build lively templates in React using Storybooks, allowing them to
get creative with their email designs. Storybooks provide a live development environment for building UI components,
The email system functionality allows developers to build lively templates in React using React.Email, allowing them to
get creative with their email designs. React.Email provide a live development environment for building UI components,
making it easier to develop and test email templates before they are sent.

### Standard email templates

The functionality includes a set of ready-to-send transactional emails, such as *new user verification*, *password
recovery*, *subscription renewals* making it easy for developers to get started.
The functionality includes a set of ready-to-send transactional emails, such as _new user verification_, _password
recovery_, _subscription renewals_ making it easy for developers to get started.

### Creating new email templates
Comment thread
sdrejkarz marked this conversation as resolved.

Expand All @@ -29,7 +29,7 @@ emails at the most optimal time for their users.

### Sending test emails

Sending test emails is made easy with the ability to send test emails directly from a Storybook to Mailcatcher service.
Sending test emails is made easy with the ability to send test emails directly from a React.Email via Resend.

@sdrejkarz sdrejkarz May 23, 2024

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Built-in Resend works fine, but I haven't managed to send the image (logo).

This allows developers to debug and test their email templates before they are sent to users.

### Internationalization Support
Expand Down
2 changes: 1 addition & 1 deletion packages/webapp-libs/webapp-emails/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ export default {
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageReporters: ['lcov'],
coveragePathIgnorePatterns: ['/node_modules/', '.*.svg'],
setupFilesAfterEnv: ['./src/tests/setupTests.ts'],
setupFilesAfterEnv: ['./src/tests/setupTests.ts', './jest.polyfills.js'],
watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],
};
18 changes: 18 additions & 0 deletions packages/webapp-libs/webapp-emails/jest.polyfills.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// jest.polyfills.js
/**
* @note The block below contains polyfills for Node.js globals
* required for Jest to function when running JSDOM tests.
* These HAVE to be require's and HAVE to be in this exact
* order, since "undici" depends on the "TextEncoder" global API.
*
* Consider migrating to a more modern test runner if
* you don't want to deal with this.
*/

const { TextDecoder, TextEncoder } = require('node:util');

// eslint-disable-next-line no-undef
Object.defineProperties(globalThis, {
TextDecoder: { value: TextDecoder },
TextEncoder: { value: TextEncoder },
});
9 changes: 7 additions & 2 deletions packages/webapp-libs/webapp-emails/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
"test": "jest",
"test:watch": "jest --watch",
"lint": "eslint .",
"type-check": "tsc --noEmit --project tsconfig.lib.json"
"type-check": "tsc --noEmit --project tsconfig.lib.json",
"preview": "email dev --dir src/preview -p 1081 "
},
"dependencies": {
"@react-email/components": "0.0.18",
"@react-email/font": "0.0.6",
"@react-email/tailwind": "0.0.17",
"@sb/webapp-api-client": "workspace:*",
"@sb/webapp-core": "workspace:*",
"@sb/webapp-finances": "workspace:*",
"juice": "^8.1.0"
"juice": "^8.1.0",
"react-email": "2.1.3"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

niticking: I'm thinking of moving react-email to the devDependencies, as it will be used only locally.

},
"devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
Expand Down
34 changes: 0 additions & 34 deletions packages/webapp-libs/webapp-emails/src/base/base.styles.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,12 @@ import { Button } from '../';
import { render } from '../../../tests/utils/rendering';

const linkTo = '/example';
const testId = 'test-button';
const testId = 'button-testId';
const buttonText = 'Click me!';

describe('Button', () => {
test('should render button with correct href attribute', async () => {
render(
<Button linkTo={linkTo} data-testid={testId}>
{buttonText}
</Button>
);
render(<Button linkTo={linkTo}>{buttonText}</Button>);

const button = await screen.findByTestId(testId);
expect(button.getAttribute('href')).toEqual(`${process.env.PUBLIC_URL}${linkTo}`);
Expand All @@ -22,11 +18,7 @@ describe('Button', () => {
test('should render external link', async () => {
const externalLink = 'https://example.com';

render(
<Button linkTo={externalLink} data-testid={testId}>
{buttonText}
</Button>
);
render(<Button linkTo={externalLink}>{buttonText}</Button>);

const button = await screen.findByTestId(testId);
expect(button.getAttribute('href')).toEqual(externalLink);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Button as ButtonComponent } from '@react-email/components';
import { ENV } from '@sb/webapp-core/config/env';
import { cn } from '@sb/webapp-core/lib/utils';
import { HTMLAttributes } from 'react';

import { Container } from './button.styles';

export type ButtonProps = HTMLAttributes<HTMLAnchorElement> & {
linkTo: string;
};
Expand All @@ -12,8 +12,12 @@ export const Button = (props: ButtonProps) => {
const hrefUrl = isExternalLink ? props.linkTo : `${ENV.PUBLIC_URL}${props.linkTo}`;

return (
<Container {...props} href={hrefUrl}>
<ButtonComponent
href={hrefUrl}
className={cn('bg-black text-white rounded font-custom text-sm p-2', props.className)}
data-testid="button-testId"
>
{props.children}
</Container>
</ButtonComponent>
);
};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Img } from '@react-email/components';
import { ENV } from '@sb/webapp-core/config/env';
import { HTMLAttributes } from 'react';
import { ComponentPropsWithoutRef } from 'react';

export type ImageProps = HTMLAttributes<HTMLImageElement> & {
src: string;
};
export type ImageProps = ComponentPropsWithoutRef<'img'>;

const baseURL = ENV.EMAIL_ASSETS_URL || `/static`;

export const Image = (props: ImageProps) => {
return <img alt="" {...props} src={`${ENV.EMAIL_ASSETS_URL}/${props.src}`} />;
return <Img alt="" {...props} src={`${baseURL}/${props.src}`} />;
};

Image.defaultProps = {
Expand Down
2 changes: 0 additions & 2 deletions packages/webapp-libs/webapp-emails/src/base/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export * from './base.styles';

export { Image } from './image';
export { Layout } from './layout';
export { Button } from './button';
4 changes: 3 additions & 1 deletion packages/webapp-libs/webapp-emails/src/base/layout/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { Layout } from './layout.component';
import Layout from './layout.component';

export { Layout };
Original file line number Diff line number Diff line change
@@ -1,35 +1,69 @@
import { Body, Container, Font, Head, Heading, Html, Section, Tailwind, Text } from '@react-email/components';
import { ENV } from '@sb/webapp-core/config/env';
import { fontFamily, fontWeight } from '@sb/webapp-core/theme';
import { ReactNode } from 'react';

import { Image } from '../image';
import { Container, Table, Td, Text, Title, Tr } from './layout.styles';

export type LayoutProps = {
title: ReactNode;
text: ReactNode;
children?: ReactNode;
};

export const Layout = ({ title, text, children }: LayoutProps) => {
const baseURL = ENV.EMAIL_ASSETS_URL || `/static`;

export default function Layout({ title, text, children }: LayoutProps) {
return (
<Container>
<Table>
<Tr>
<Td>
<Image style={{ display: 'block', margin: '0 auto', width: 256 }} src="logo.png" />
</Td>
</Tr>
<Tr>
<Title>{title}</Title>
</Tr>

<Tr>
<Text>{text}</Text>
</Tr>

<Tr>
<Td>{children}</Td>
</Tr>
</Table>
</Container>
<Html>
<Head>
<Font
fontFamily={fontFamily.primary}
fallbackFontFamily="Verdana"
webFont={{
url: `${baseURL}/Inter-Regular.woff`,
format: 'woff',
}}
fontWeight={fontWeight.regular}
fontStyle="normal"
/>

<Font
fontFamily={fontFamily.primary}
fallbackFontFamily="Verdana"
webFont={{
url: `${baseURL}/Inter-SemiBold.woff`,
format: 'woff',
}}
fontWeight={fontWeight.bold}
fontStyle="normal"
/>
</Head>
<Tailwind
config={{
theme: {
extend: {
fontFamily: {
custom: [fontFamily.primary],
},
},
},
}}
>
<Body className="bg-white my-auto mx-auto font-sans p-8">
<Container className="border border-solid border-[#eaeaea] rounded mx-auto max-w-[548px] p-10">
<Image src="logo.png" width={256} className="mt-0 mx-auto" />

<Heading className="text-black text-[24px] text-center p-0 my-[30px] mx-0 font-custom font-bold">
{title}
</Heading>

<Text className="text-black text-[14px] leading-[24px] text-center font-custom">{text}</Text>

<Section className="text-center">{children}</Section>
</Container>
</Body>
</Tailwind>
</Html>
);
};
}

This file was deleted.

Loading