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

feat: Add email template for inviting user to workspace #480

Merged
Show file tree
Hide file tree
Changes from 6 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
130 changes: 130 additions & 0 deletions apps/api/src/mail/emails/workspace-invitation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import * as React from 'react'
import {
Body,
Button,
Container,
Head,
Heading,
Html,
Link,
Preview,
Section,
Text
} from '@react-email/components'
import dayjs from 'dayjs'
import {
container,
content,
ctaButton,
footer,
footerText,
h1,
link,
main,
text,
workspaceDetails,
workspaceInfo
} from '../styles/common-styles'

interface WorkspaceInvitationEmailProps {
projectName: string
projectUrl: string
invitedBy: string
invitedOn: string
forRegisteredUser: boolean
}

export const WorkspaceInvitationEmail = ({
projectName,
projectUrl,
invitedBy,
invitedOn,
forRegisteredUser
}: WorkspaceInvitationEmailProps) => {
const formattedInvitedOnDate = dayjs(invitedOn).format(
'ddd, MMM D, YYYY h:mm A'
)

return (
<Html>
<Head />
<Preview>
{forRegisteredUser
? 'Welcome Back! Join Your Workspace'
: 'You are Invited to Join the Workspace'}
</Preview>
<Body style={main}>
<Container style={container}>
Allan2000-Git marked this conversation as resolved.
Show resolved Hide resolved
<Section style={content}>
<Heading style={h1}>
{forRegisteredUser
? 'Welcome Back! Join Your Workspace'
: 'You are Invited to Join the Workspace'}
</Heading>
<Text style={text}>Dear User,</Text>
<Text style={text}>
We're excited to inform you that you've been invited to join a
project on Keyshade. Here are the details of your invitation:
</Text>
<Section style={workspaceDetails}>
<Text style={workspaceInfo}>
<strong>Workspace Name:</strong> {projectName}
</Text>
<Text style={workspaceInfo}>
<strong>Invited By:</strong> {invitedBy}
</Text>
<Text style={workspaceInfo}>
<strong>Invited On:</strong> {invitedOn}
</Text>
<Text style={workspaceInfo}>
<strong>Invited On:</strong> {formattedInvitedOnDate}
</Text>
</Section>
<Text style={text}>
Join the project by clicking the button below - we're excited to
have you!
</Text>
<Button href={projectUrl} style={ctaButton}>
Get started
</Button>
<Text style={text}>
If you believe this action was taken in error or have any
questions regarding this change, please contact your project
administrator or our support team.
</Text>
<Text style={text}>
We appreciate your understanding and thank you for your
contributions to the project.
</Text>
<Text style={text}>
Cheers,
<br />
Team Keyshade
</Text>
</Section>
<Section style={footer}>
<Text style={footerText}>
This is an automated message. Please do not reply to this email.
</Text>
<Text style={footerText}>
Read our{' '}
<Link href="https://www.keyshade.xyz/privacy" style={link}>
Privacy Policy
</Link>{' '}
and{' '}
<Link
href="https://www.keyshade.xyz/terms_and_condition"
style={link}
>
Terms and Conditions
</Link>{' '}
for more information on how we manage your data and services.
</Text>
</Section>
</Container>
</Body>
</Html>
)
}

export default WorkspaceInvitationEmail
74 changes: 12 additions & 62 deletions apps/api/src/mail/emails/workspace-removal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ import {
Text
} from '@react-email/components'
import dayjs from 'dayjs'
import {
container,
content,
footer,
footerText,
h1,
link,
main,
text,
workspaceDetails,
workspaceInfo
} from '../styles/common-styles'

interface WorkspaceRemovalEmailProps {
workspaceName: string
Expand Down Expand Up @@ -87,65 +99,3 @@ export const RemovedFromWorkspaceEmail = ({
}

export default RemovedFromWorkspaceEmail

const main = {
fontFamily: "'Segoe UI', 'Roboto', sans-serif",
lineHeight: '1.6',
color: '#04050a',
backgroundColor: '#fafafa',
margin: '0',
padding: '20px'
}

const container = {
maxWidth: '600px',
margin: '0 auto',
backgroundColor: '#fff',
borderRadius: '5px',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.05)'
}

const content = {
padding: '20px 40px'
}

const h1 = {
color: '#000',
marginBottom: '20px',
fontSize: '24px',
fontWeight: '600'
}

const text = {
marginBottom: '5px',
color: '#666'
}

const workspaceDetails = {
width: '100%',
backgroundColor: '#fafafa',
borderRadius: '5px',
margin: '20px 0px',
padding: '10px 20px'
}

const workspaceInfo = {
margin: '7px 0px'
}

const footer = {
borderTop: '1px solid #eaeaea',
padding: '20px'
}

const footerText = {
fontSize: '12px',
color: '#999',
textAlign: 'center' as const,
margin: '0'
}

const link = {
color: '#000',
textDecoration: 'underline'
}
7 changes: 4 additions & 3 deletions apps/api/src/mail/services/interface.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ export interface IMailService {

sendEmailChangedOtp(email: string, otp: string): Promise<void>

workspaceInvitationMailForUsers(
invitedToWorkspace(
email: string,
workspace: string,
actionUrl: string,
projectName: string,
Allan2000-Git marked this conversation as resolved.
Show resolved Hide resolved
projectUrl: string,
invitedBy: string,
invitedOn: string,
forRegisteredUser: boolean
): Promise<void>

Expand Down
43 changes: 19 additions & 24 deletions apps/api/src/mail/services/mail.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IMailService } from './interface.service'
import { Transporter, createTransport } from 'nodemailer'
import RemovedFromWorkspaceEmail from '../emails/workspace-removal'
import { render } from '@react-email/render'
import WorkspaceInvitationEmail from '../emails/workspace-invitation'

@Injectable()
export class MailService implements IMailService {
Expand All @@ -24,33 +25,27 @@ export class MailService implements IMailService {
}
})
}
async workspaceInvitationMailForUsers(
async invitedToWorkspace(
email: string,
workspace: string,
actionUrl: string,
invitee: string,
projectName: string,
projectUrl: string,
invitedBy: string,
invitedOn: string,
forRegisteredUser: boolean
): Promise<void> {
const subject = `You have been invited to a ${workspace}`
const intro = forRegisteredUser
? `Hello again! You've been invited to join a new workspace.`
: `Hello there! We're excited to welcome you to Keyshade.`
const body = `<!DOCTYPE html>
<html>
<head>
<title>Workspace Invitation</title>
</head>
<body>
<h1>Welcome to keyshade!</h1>
<p>${intro}</p>
<p>You have been invited to join the workspace <strong>${workspace}</strong> by <strong>${invitee}</strong>.</p>
<p>Please click on the link below to accept the invitation.</p>
<p><a href="${actionUrl}">Accept Invitation</a></p>
<p>Thank you for choosing us.</p>
<p>Best Regards,</p>
<p>keyshade Team</p>
</body>
</html>`
const subject = forRegisteredUser
? 'Welcome Back! Join Your Workspace'
: 'You are Invited to Join the Workspace'

const body = await render(
WorkspaceInvitationEmail({
projectName,
projectUrl,
invitedBy,
invitedOn,
forRegisteredUser
})
)
await this.sendEmail(email, subject, body)
}

Expand Down
13 changes: 7 additions & 6 deletions apps/api/src/mail/services/mock.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ import { IMailService } from './interface.service'
export class MockMailService implements IMailService {
private readonly log = new Logger(MockMailService.name)

async workspaceInvitationMailForUsers(
async invitedToWorkspace(
email: string,
workspace: string,
actionUrl: string,
invitee: string,
projectName: string,
projectUrl: string,
invitedBy: string,
invitedOn: string,
forRegisteredUser: boolean
): Promise<void> {
this.log.log(
forRegisteredUser
? `Workspace Invitation Mail for Registered User: ${email}, ${workspace}, ${actionUrl}, ${invitee}`
: `Workspace Invitation Mail for Non Registered User: ${email}, ${workspace}, ${actionUrl}, ${invitee}`
? `User ${email} has been invited to the workspace ${projectName} by ${invitedBy} on ${invitedOn}. Invitation details can be accessed at ${projectUrl}.`
: `User ${email} has been invited to the workspace ${projectName} by ${invitedBy} on ${invitedOn}. Since the user is not registered, they can sign up and access the invitation details at ${projectUrl}.`
)
}

Expand Down
78 changes: 78 additions & 0 deletions apps/api/src/mail/styles/common-styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { CSSProperties } from 'react'

export const main: CSSProperties = {
fontFamily: "'Segoe UI', 'Roboto', sans-serif",
lineHeight: '1.6',
color: '#04050a',
backgroundColor: '#fafafa',
margin: '0',
padding: '20px'
}

export const container: CSSProperties = {
maxWidth: '600px',
margin: '0 auto',
backgroundColor: '#fff',
borderRadius: '5px',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.05)'
}

export const content: CSSProperties = {
padding: '20px 40px'
}

export const h1: CSSProperties = {
color: '#000',
marginBottom: '20px',
fontSize: '24px',
fontWeight: '600'
}

export const text: CSSProperties = {
marginBottom: '5px',
color: '#666'
}

export const workspaceDetails: CSSProperties = {
width: '100%',
backgroundColor: '#fafafa',
borderRadius: '5px',
margin: '20px 0px',
padding: '10px 20px'
}

export const workspaceInfo: CSSProperties = {
margin: '7px 0px'
}

export const ctaButton: CSSProperties = {
width: '100px',
color: '#ffffff',
fontSize: '14px',
fontWeight: '500',
textAlign: 'center',
marginTop: '10px',
cursor: 'pointer',
display: 'inline-block',
backgroundColor: '#000',
textDecoration: 'none',
padding: '10px 22px',
borderRadius: '5px'
}

export const footer: CSSProperties = {
borderTop: '1px solid #eaeaea',
padding: '20px'
}

export const footerText: CSSProperties = {
fontSize: '12px',
color: '#999',
textAlign: 'center' as const,
margin: '0'
}

export const link: CSSProperties = {
color: '#000',
textDecoration: 'underline'
}
Loading
Loading