Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React-email e componentização de emails #1606

Merged
merged 1 commit into from
Feb 10, 2024
Merged
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
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ Created migration -- ./infra/migrations/1655399502254_alter-table-users-add-tabc

Caso esta nova migração esteja válida, ela será automaticamente executada na próxima vez que você rodar o comando `npm run dev`. Caso contrário, o serviço não irá subir e os logs de erro estarão registrados no arquivo `migrations.log` encontrado na raiz do projeto.

### Templates de email

Os templates de email estão localizados em `models/transactional/emails`, eles utilizam o [react-email](https://react.email/) para a composição do layout e renderização.

Para visualizar e testar os templates, você pode utilizar o comando:

```bash
npm run email
```

### Commit das alterações

Após finalizar suas alterações e se certificar que todos os testes estão passando com o comando geral `npm test`, chegou a hora de fazer o commit das suas alterações.
Expand Down
3 changes: 2 additions & 1 deletion infra/email.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ if (!webserver.isServerlessRuntime) {

const transporter = nodemailer.createTransport(transporterConfiguration);

async function send({ from, to, subject, text }) {
async function send({ from, to, subject, html, text }) {
const mailOptions = {
from: from,
to: to,
subject: subject,
html: html,
text: text,
};

Expand Down
17 changes: 8 additions & 9 deletions models/activation.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import database from 'infra/database.js';
import email from 'infra/email.js';
import webserver from 'infra/webserver.js';
import authorization from 'models/authorization.js';
import { ActivationEmail } from 'models/transactional';
import user from 'models/user.js';

async function createAndSendActivationEmail(user) {
Expand All @@ -24,22 +25,20 @@ async function create(user) {
async function sendEmailToUser(user, tokenId) {
const activationPageEndpoint = getActivationPageEndpoint(tokenId);

const { html, text } = ActivationEmail({
username: user.username,
activationLink: activationPageEndpoint,
});

await email.send({
from: {
name: 'TabNews',
address: '[email protected]',
},
to: user.email,
subject: 'Ative seu cadastro no TabNews',
text: `${user.username}, clique no link abaixo para ativar seu cadastro no TabNews:
${activationPageEndpoint}
Caso você não tenha feito esta requisição, ignore esse email.
Atenciosamente,
Equipe TabNews
Rua Antônio da Veiga, 495, Blumenau, SC, 89012-500`,
html,
text,
});
}

Expand Down
17 changes: 8 additions & 9 deletions models/email-confirmation.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NotFoundError } from 'errors';
import database from 'infra/database.js';
import email from 'infra/email.js';
import webserver from 'infra/webserver.js';
import { ConfirmationEmail } from 'models/transactional';
import user from 'models/user.js';

async function createAndSendEmail(userId, newEmail, options) {
Expand Down Expand Up @@ -32,22 +33,20 @@ async function create(userId, newEmail, options) {
async function sendEmailToUser(user, newEmail, tokenId) {
const emailConfirmationPageEndpoint = getEmailConfirmationPageEndpoint(tokenId);

const { html, text } = ConfirmationEmail({
username: user.username,
confirmationLink: emailConfirmationPageEndpoint,
});

await email.send({
from: {
name: 'TabNews',
address: '[email protected]',
},
to: newEmail,
subject: 'Confirme seu novo email',
text: `${user.username}, uma alteração de email foi solicitada.
Clique no link abaixo para confirmar esta alteração:
${emailConfirmationPageEndpoint}
Atenciosamente,
Equipe TabNews
Rua Antônio da Veiga, 495, Blumenau, SC, 89012-500`,
html,
text,
});
}

Expand Down
22 changes: 11 additions & 11 deletions models/notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import email from 'infra/email.js';
import webserver from 'infra/webserver.js';
import authorization from 'models/authorization.js';
import content from 'models/content.js';
import { NotificationEmail } from 'models/transactional';
import user from 'models/user.js';

async function sendReplyEmailToParentUser(createdContent) {
Expand Down Expand Up @@ -43,22 +44,21 @@ async function sendReplyEmailToParentUser(createdContent) {
rootContent: secureRootContent,
});

const { html, text } = NotificationEmail({
username: parentContentUser.username,
bodyReplyLine: bodyReplyLine,
contentLink: childContendUrl,
});

await email.send({
to: parentContentUser.email,
from: {
name: 'TabNews',
address: '[email protected]',
},
subject: subject,
text: `Olá, ${parentContentUser.username}!
${bodyReplyLine}
${childContendUrl}
Atenciosamente,
Equipe TabNews
Rua Antônio da Veiga, 495, Blumenau, SC, 89012-500`,
html,
text,
});
}
}
Expand All @@ -72,10 +72,10 @@ function getSubject({ createdContent, rootContent }) {

function getBodyReplyLine({ createdContent, rootContent }) {
if (createdContent.parent_id === rootContent.id) {
return `"${createdContent.owner_username}" respondeu à sua publicação "${rootContent.title}". Para ler a resposta, utilize o link abaixo:`;
return `"${createdContent.owner_username}" respondeu à sua publicação "${rootContent.title}".`;
}

return `"${createdContent.owner_username}" respondeu ao seu comentário na publicação "${rootContent.title}". Para ler a resposta, utilize o link abaixo:`;
return `"${createdContent.owner_username}" respondeu ao seu comentário na publicação "${rootContent.title}".`;
}

function getChildContendUrl({ owner_username, slug }) {
Expand Down
17 changes: 8 additions & 9 deletions models/recovery.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import database from 'infra/database.js';
import email from 'infra/email.js';
import webserver from 'infra/webserver.js';
import session from 'models/session';
import { RecoveryEmail } from 'models/transactional';
import user from 'models/user.js';

async function createAndSendRecoveryEmail(secureInputValues) {
Expand Down Expand Up @@ -49,22 +50,20 @@ async function create(user) {
async function sendEmailToUser(user, tokenId) {
const recoverPageEndpoint = getRecoverPageEndpoint(tokenId);

const { html, text } = RecoveryEmail({
username: user.username,
recoveryLink: recoverPageEndpoint,
});

await email.send({
from: {
name: 'TabNews',
address: '[email protected]',
},
to: user.email,
subject: 'Recuperação de Senha',
text: `${user.username}, foi solicitada uma recuperação de senha. Caso você não tenha feito a solicitação, ignore esse email.
Caso você tenha feito essa solicitação, clique no link abaixo para definir uma nova senha:
${recoverPageEndpoint}
Atenciosamente,
Equipe TabNews
Rua Antônio da Veiga, 495, Blumenau, SC, 89012-500`,
html,
text,
});
}

Expand Down
60 changes: 60 additions & 0 deletions models/transactional/components/default-layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Body, Container, Head, Heading, Html, Img, Preview, Text } from '@react-email/components';

export const DefaultLayoutText = ({ username, content }) => {
return `Olá, ${username}!
${content}
Atenciosamente,
Equipe TabNews
Rua Antônio da Veiga, 495, Blumenau, SC, 89012-500`;
};

export const DefaultLayout = ({ username, previewText, children }) => (
Rafatcb marked this conversation as resolved.
Show resolved Hide resolved
<Html>
<Head />
<Preview>{previewText}</Preview>
<Body style={main}>
<Container style={container}>
<Heading style={h1}>Olá, {username}!</Heading>

{children}

<Text style={footer}>
Atenciosamente, <br />
Equipe TabNews <br />
Rua Antônio da Veiga, 495, Blumenau, SC, 89012-500
</Text>
<Img src="https://www.tabnews.com.br/favicon.png" width="32" height="32" alt="TabNews" />
</Container>
</Body>
</Html>
);

const main = {
backgroundColor: '#ffffff',
fontFamily:
"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
};

const container = {
paddingLeft: '12px',
paddingRight: '12px',
margin: '0 auto',
};

const h1 = {
color: '#333',
fontSize: '24px',
fontWeight: 'bold',
margin: '40px 0',
padding: '0',
};

const footer = {
color: '#898989',
fontSize: '12px',
lineHeight: '22px',
marginTop: '12px',
marginBottom: '4px',
};
58 changes: 58 additions & 0 deletions models/transactional/emails/activation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Link, Text } from '@react-email/components';

import { DefaultLayout, DefaultLayoutText } from '../components/default-layout';

export const ActivationEmailText = ({ username, activationLink }) => {
const content = `Clique no link abaixo para ativar seu cadastro no TabNews:
${activationLink}
Caso você não tenha feito esta requisição, ignore esse email.`;

return DefaultLayoutText({ username, content });
};

export const ActivationEmailHtml = ({ username, activationLink }) => (
<DefaultLayout username={username} previewText="Ative seu cadastro no TabNews">
<Link href={activationLink} style={link}>
Clique aqui para ativar seu cadastro no TabNews.
</Link>

<Text style={text}>Se você não conseguir clicar no link, copie e cole o endereço abaixo no seu navegador:</Text>

<code style={code}>{activationLink}</code>

<Text style={text}>Caso você não tenha feito esta requisição, ignore esse email.</Text>
</DefaultLayout>
);

ActivationEmailHtml.PreviewProps = {
username: 'User',
activationLink: 'https://tabnews.com.br/cadastro/ativar/TOKEN_ID',
};

export default ActivationEmailHtml;

const link = {
color: '#2754C5',
fontSize: '14px',
textDecoration: 'underline',
display: 'block',
marginBottom: '16px',
};

const text = {
color: '#333',
fontSize: '14px',
margin: '24px 0',
};

const code = {
backgroundColor: '#f3f3f3',
color: '#333',
display: 'block',
fontSize: '14px',
padding: '12px',
borderRadius: '8px',
wordBreak: 'break-all',
};
62 changes: 62 additions & 0 deletions models/transactional/emails/confirmation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Link, Text } from '@react-email/components';

import { DefaultLayout, DefaultLayoutText } from '../components/default-layout';

export const ConfirmationEmailText = ({ username, confirmationLink }) => {
const content = `Uma alteração de email foi solicitada.
Clique no link abaixo para confirmar esta alteração:
${confirmationLink}
Caso você não tenha feito esta requisição, ignore esse email.`;

return DefaultLayoutText({ username, content });
};

export const ConfirmationEmailHtml = ({ username, confirmationLink }) => (
<DefaultLayout username={username} previewText="Alteração de email no Tabnews">
<Text style={text}>Uma alteração de email foi solicitada.</Text>

<Link href={confirmationLink} style={link}>
Clique aqui para confirmar esta alteração.
</Link>

<Text style={text}>Se você não conseguir clicar no link, copie e cole o endereço abaixo no seu navegador:</Text>

<code style={code}>{confirmationLink}</code>

<Text style={text}>Caso você não tenha feito esta requisição, ignore esse email.</Text>
</DefaultLayout>
);

ConfirmationEmailHtml.PreviewProps = {
username: 'User',
confirmationLink: 'https://tabnews.com.br/perfil/confirmar-email/TOKEN_ID',
};

export default ConfirmationEmailHtml;

const link = {
color: '#2754C5',
fontSize: '14px',
textDecoration: 'underline',
display: 'block',
marginBottom: '16px',
};

const text = {
color: '#333',
fontSize: '14px',
margin: '24px 0',
};

const code = {
backgroundColor: '#f3f3f3',
color: '#333',
display: 'block',
fontSize: '14px',
padding: '12px',
borderRadius: '8px',
wordBreak: 'break-all',
};
Loading
Loading