feat: move rendering to runtime
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
@@ -1,73 +0,0 @@
|
||||
import {
|
||||
Body,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Preview,
|
||||
Img,
|
||||
Text,
|
||||
Button, Section,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import RootWrapper from '../components/RootWrapper';
|
||||
import { Footer } from '../components/Footer';
|
||||
import ContentWrapper from '../components/ContentWrapper';
|
||||
import { baseUrl } from '../utils/constant';
|
||||
|
||||
// Corresponding ejs template
|
||||
interface Props {
|
||||
workspaceTitle: string;
|
||||
baseTitle: string;
|
||||
role: string;
|
||||
name: string;
|
||||
email: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const BaseRoleUpdateEE = () => (
|
||||
<Html>
|
||||
<RootWrapper>
|
||||
<Head />
|
||||
<Preview>Your base role has been updated</Preview>
|
||||
<Body className="bg-white">
|
||||
<ContentWrapper>
|
||||
<Heading className="text-gray-900 text-center font-bold m-auto text-xl md:text-2xl">
|
||||
Your base role has been updated
|
||||
</Heading>
|
||||
<Section className="py-6 text-center">
|
||||
<span className="font-bold text-gray-900 text-base">
|
||||
{'<%= workspaceTitle %>'}
|
||||
</span>
|
||||
<span className="px-2 text-gray-700">
|
||||
/
|
||||
</span>
|
||||
<span className="font-bold text-gray-900 text-base">
|
||||
{'<%= baseTitle %>'}
|
||||
</span>
|
||||
</Section>
|
||||
<Section className="pb-6 text-center">
|
||||
<Img
|
||||
src={`${baseUrl}/badges/<%= role %>.png`}
|
||||
alt="<%= role %>" className="h-7 mx-auto" />
|
||||
</Section>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
Your access in
|
||||
<span className="font-bold text-gray-800">
|
||||
{' <%= baseTitle %> '}
|
||||
</span>
|
||||
has been updated to <span className="font-bold text-gray-800">{'<%= role %> '}</span>
|
||||
by <span className="font-bold text-gray-800">{' <%= name %>'}</span> ( {'<%= email %>'})
|
||||
</Text>
|
||||
<Button className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10" href="<%= link %>">
|
||||
<Text className="!my-[8px]">
|
||||
Go to Base
|
||||
</Text>
|
||||
</Button>
|
||||
</ContentWrapper>
|
||||
<Footer />
|
||||
</Body>
|
||||
</RootWrapper>
|
||||
|
||||
</Html>
|
||||
);
|
||||
export default BaseRoleUpdateEE;
|
||||
@@ -1,64 +0,0 @@
|
||||
import {
|
||||
Body,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Preview,
|
||||
Img,
|
||||
Text,
|
||||
Button, Section,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import RootWrapper from '../components/RootWrapper';
|
||||
import { Footer } from '../components/Footer';
|
||||
import ContentWrapper from '../components/ContentWrapper';
|
||||
import { baseUrl } from '../utils/constant';
|
||||
|
||||
// Corresponding ejs template
|
||||
interface Props {
|
||||
baseTitle: string;
|
||||
role: string;
|
||||
name: string;
|
||||
email: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const BaseRoleUpdate = () => (
|
||||
<Html>
|
||||
<RootWrapper>
|
||||
<Head />
|
||||
<Preview>Your base role has been updated</Preview>
|
||||
<Body className="bg-white">
|
||||
<ContentWrapper>
|
||||
<Heading className="text-gray-900 text-center font-bold m-auto text-xl md:text-2xl">
|
||||
Your base role has been updated
|
||||
</Heading>
|
||||
<Section className="py-6 text-center font-bold text-gray-900 text-base">
|
||||
{'<%= baseTitle %>'}
|
||||
</Section>
|
||||
<Section className="pb-6 text-center">
|
||||
<Img
|
||||
src={`${baseUrl}/badges/<%= role %>.png`}
|
||||
alt="<%= role %>" className="h-7 mx-auto" />
|
||||
</Section>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
Your access in
|
||||
<span className="font-bold text-gray-800">
|
||||
{' <%= baseTitle %> '}
|
||||
</span>
|
||||
has been updated to <span className="font-bold text-gray-800">{'<%= role %> '}</span>
|
||||
by <span className="font-bold text-gray-800">{' <%= name %>'}</span> ( {'<%= email %>'})
|
||||
</Text>
|
||||
<Button className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10" href="<%= link %>">
|
||||
<Text className="!my-[8px]">
|
||||
Go to Base
|
||||
</Text>
|
||||
</Button>
|
||||
</ContentWrapper>
|
||||
<Footer />
|
||||
</Body>
|
||||
</RootWrapper>
|
||||
|
||||
</Html>
|
||||
);
|
||||
export default BaseRoleUpdate;
|
||||
@@ -1,63 +0,0 @@
|
||||
import {
|
||||
Body,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Preview,
|
||||
Text,
|
||||
Button, Section,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import RootWrapper from '../components/RootWrapper';
|
||||
import { Footer } from '../components/Footer';
|
||||
import ContentWrapper from '../components/ContentWrapper';
|
||||
|
||||
// Corresponding ejs template
|
||||
interface Props {
|
||||
name: string;
|
||||
email: string;
|
||||
link: string;
|
||||
workspaceTitle: string;
|
||||
baseTitle: string;
|
||||
}
|
||||
|
||||
export const MentionCommentEE = () => (
|
||||
<Html>
|
||||
<RootWrapper>
|
||||
<Head />
|
||||
<Preview>You have been mentioned</Preview>
|
||||
<Body className="bg-white">
|
||||
<ContentWrapper>
|
||||
<Heading className="text-gray-900 text-center font-bold m-auto text-xl md:text-2xl">
|
||||
You have been mentioned
|
||||
</Heading>
|
||||
<Section className="py-6 text-center">
|
||||
<span className="font-bold text-gray-900 text-base">
|
||||
{'<%= workspaceTitle %>'}
|
||||
</span>
|
||||
<span className="px-2 text-gray-700">
|
||||
/
|
||||
</span>
|
||||
<span className="font-bold text-gray-900 text-base">
|
||||
{'<%= baseTitle %>'}
|
||||
</span>
|
||||
</Section>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
{'<%= name %>'} ({'<%= email %>'}) has mentioned you in
|
||||
<span className="font-semibold text-gray-800">
|
||||
{' <%= baseTitle %>'}
|
||||
</span>
|
||||
</Text>
|
||||
<Button className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10" href="<%= link %>">
|
||||
<Text className="!my-[8px]">
|
||||
View Comment
|
||||
</Text>
|
||||
</Button>
|
||||
</ContentWrapper>
|
||||
<Footer />
|
||||
</Body>
|
||||
</RootWrapper>
|
||||
|
||||
</Html>
|
||||
);
|
||||
export default MentionCommentEE;
|
||||
@@ -1,61 +0,0 @@
|
||||
import {
|
||||
Body,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Preview,
|
||||
Text,
|
||||
Button, Section,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import RootWrapper from '../components/RootWrapper';
|
||||
import { Footer } from '../components/Footer';
|
||||
import ContentWrapper from '../components/ContentWrapper';
|
||||
|
||||
// Corresponding ejs template
|
||||
interface Props {
|
||||
name: string;
|
||||
email: string;
|
||||
tableTitle: string;
|
||||
link: string;
|
||||
workspaceTitle: string;
|
||||
baseTitle: string;
|
||||
}
|
||||
|
||||
export const MentionRowEE = () => (
|
||||
<Html>
|
||||
<RootWrapper>
|
||||
<Head />
|
||||
<Preview>You have been mentioned</Preview>
|
||||
<Body className="bg-white">
|
||||
<ContentWrapper>
|
||||
<Heading className="text-gray-900 text-center font-bold m-auto text-xl md:text-2xl">
|
||||
You have been mentioned
|
||||
</Heading>
|
||||
<Section className="py-6 text-center">
|
||||
<span className="font-bold text-gray-900 text-base">
|
||||
{'<%= workspaceTitle %>'}
|
||||
</span>
|
||||
<span className="px-2 text-gray-700">
|
||||
/
|
||||
</span>
|
||||
<span className="font-bold text-gray-900 text-base">
|
||||
{'<%= baseTitle %>'}
|
||||
</span>
|
||||
</Section>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
{'<%= name %>'} ({'<%= email %>'}) has mentioned you in a record in the <span className="font-semibold text-gray-800"> {'<%= tableTitle %>'}
|
||||
</span> table.
|
||||
</Text>
|
||||
<Button className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10" href="<%= link %>">
|
||||
<Text className="!my-[8px]">
|
||||
View Record
|
||||
</Text>
|
||||
</Button>
|
||||
</ContentWrapper>
|
||||
<Footer />
|
||||
</Body>
|
||||
</RootWrapper>
|
||||
</Html>
|
||||
);
|
||||
export default MentionRowEE;
|
||||
@@ -1,51 +0,0 @@
|
||||
import {
|
||||
Body,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Preview,
|
||||
Text,
|
||||
Button, Section,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import RootWrapper from '../components/RootWrapper';
|
||||
import { Footer } from '../components/Footer';
|
||||
import ContentWrapper from '../components/ContentWrapper';
|
||||
|
||||
// Corresponding ejs template
|
||||
interface Props {
|
||||
name: string;
|
||||
email: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const OrganizationInvite = () => (
|
||||
<Html>
|
||||
<RootWrapper>
|
||||
<Head />
|
||||
<Preview>You’ve been invited to NocoDB</Preview>
|
||||
<Body className="bg-white">
|
||||
<ContentWrapper>
|
||||
<Heading className="text-gray-900 text-center font-bold m-auto text-xl md:text-2xl">
|
||||
You’ve been invited to NocoDB
|
||||
</Heading>
|
||||
<Section className="py-6 mx-auto font-bold mx-auto text-center text-gray-900 text-base">
|
||||
{'<%= baseTitle %>'}
|
||||
</Section>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
<span className="font-bold text-gray-800">{'<%= name %>'}</span> ( {'<%= email %>'}) has invited you to
|
||||
collaborate on NocoDB.
|
||||
</Text>
|
||||
<Button className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10" href="<%= link %>">
|
||||
<Text className="!my-[8px]">
|
||||
Go to NocoDB
|
||||
</Text>
|
||||
</Button>
|
||||
</ContentWrapper>
|
||||
<Footer />
|
||||
</Body>
|
||||
</RootWrapper>
|
||||
|
||||
</Html>
|
||||
);
|
||||
export default OrganizationInvite;
|
||||
@@ -1,56 +0,0 @@
|
||||
import {
|
||||
Body,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Preview,
|
||||
Img,
|
||||
Text,
|
||||
Button, Section,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import RootWrapper from '../components/RootWrapper';
|
||||
import { Footer } from '../components/Footer';
|
||||
import ContentWrapper from '../components/ContentWrapper';
|
||||
import { baseUrl } from '../utils/constant';
|
||||
|
||||
// Corresponding ejs template
|
||||
interface Props {
|
||||
role: string;
|
||||
name: string;
|
||||
email: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const OrganizationRoleUpdate = () => (
|
||||
<Html>
|
||||
<RootWrapper>
|
||||
<Head />
|
||||
<Preview>Your organization role has been updated</Preview>
|
||||
<Body className="bg-white">
|
||||
<ContentWrapper>
|
||||
<Heading className="text-gray-900 text-center font-bold m-auto text-xl md:text-2xl">
|
||||
Your organization role has been updated
|
||||
</Heading>
|
||||
<Section className="py-6 text-center">
|
||||
<Img
|
||||
src={`${baseUrl}/badges/<%= role %>.png`}
|
||||
alt="<%= role %>" className="h-7 mx-auto" />
|
||||
</Section>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
Your access in NocoDB has been updated to <span className="font-bold text-gray-800">{'<%= role %> '}</span>
|
||||
by <span className="font-bold text-gray-800">{' <%= name %>'}</span> ( {'<%= email %>'})
|
||||
</Text>
|
||||
<Button className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10" href="<%= link %>">
|
||||
<Text className="!my-[8px]">
|
||||
Go to NocoDB
|
||||
</Text>
|
||||
</Button>
|
||||
</ContentWrapper>
|
||||
<Footer />
|
||||
</Body>
|
||||
</RootWrapper>
|
||||
|
||||
</Html>
|
||||
);
|
||||
export default OrganizationRoleUpdate;
|
||||
@@ -1,51 +0,0 @@
|
||||
import {
|
||||
Body,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Preview,
|
||||
Text,
|
||||
Button, Section,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import RootWrapper from '../components/RootWrapper';
|
||||
import { Footer } from '../components/Footer';
|
||||
import ContentWrapper from '../components/ContentWrapper';
|
||||
|
||||
// Corresponding ejs template
|
||||
interface Props {
|
||||
workspaceTitle: string;
|
||||
name: string;
|
||||
email: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const WorkspaceInviteEe = () => (
|
||||
<Html>
|
||||
<RootWrapper>
|
||||
<Head />
|
||||
<Preview>You have been invited</Preview>
|
||||
<Body className="bg-white">
|
||||
<ContentWrapper>
|
||||
<Heading className="text-gray-900 text-center font-bold m-auto text-xl md:text-2xl">
|
||||
You’ve been invited
|
||||
</Heading>
|
||||
<Section className="py-6 mx-auto font-bold text-center text-gray-900 text-base">
|
||||
{'<%= workspaceTitle %>'}
|
||||
</Section>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
<span className="font-bold text-gray-800">{'<%= name %>'}</span> ({'<%= email %>'}) has invited you to
|
||||
join <span className="font-bold text-gray-800">{'<%= workspaceTitle %>'}</span>. Click on ‘Accept invite’ to join.
|
||||
</Text>
|
||||
<Button className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10" href="<%= link %>">
|
||||
<Text className="!my-[8px]">
|
||||
Accept invite
|
||||
</Text>
|
||||
</Button>
|
||||
</ContentWrapper>
|
||||
<Footer />
|
||||
</Body>
|
||||
</RootWrapper>
|
||||
</Html>
|
||||
);
|
||||
export default WorkspaceInviteEe;
|
||||
@@ -1,66 +0,0 @@
|
||||
import {
|
||||
Body,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Preview,
|
||||
Img,
|
||||
Text,
|
||||
Button, Section,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import RootWrapper from '../components/RootWrapper';
|
||||
import { Footer } from '../components/Footer';
|
||||
import ContentWrapper from '../components/ContentWrapper';
|
||||
import { baseUrl } from '../utils/constant';
|
||||
|
||||
// Corresponding ejs template
|
||||
interface Props {
|
||||
workspaceTitle: string;
|
||||
role: string;
|
||||
name: string;
|
||||
email: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const WorkspaceRoleUpdateEe = () => (
|
||||
<Html>
|
||||
<RootWrapper>
|
||||
<Head />
|
||||
<Preview>Your workspace role has been updated</Preview>
|
||||
<Body className="bg-white">
|
||||
<ContentWrapper>
|
||||
<Heading className="text-gray-900 text-center font-bold m-auto text-xl md:text-2xl">
|
||||
Your workspace role has been updated
|
||||
</Heading>
|
||||
<Section className="py-6 text-center">
|
||||
<span className="font-bold text-gray-900 mx-auto text-base">
|
||||
{'<%= workspaceTitle %>'}
|
||||
</span>
|
||||
</Section>
|
||||
<Section className="pb-6 text-center">
|
||||
<Img
|
||||
src={`${baseUrl}/badges/<%= role %>.png`}
|
||||
alt="<%= role %>" className="h-7 mx-auto" />
|
||||
</Section>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
Your access in
|
||||
<span className="font-bold text-gray-800">
|
||||
{' <%= workspaceTitle %> '}
|
||||
</span>
|
||||
has been updated to <span className="font-bold text-gray-800">{'<%= role %> '}</span>
|
||||
by <span className="font-bold text-gray-800">{'<%= name %>'}</span> ({'<%= email %>'})
|
||||
</Text>
|
||||
<Button className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10" href="<%= link %>">
|
||||
<Text className="!my-[8px]">
|
||||
Go to Workspace
|
||||
</Text>
|
||||
</Button>
|
||||
</ContentWrapper>
|
||||
<Footer />
|
||||
</Body>
|
||||
</RootWrapper>
|
||||
|
||||
</Html>
|
||||
);
|
||||
export default WorkspaceRoleUpdateEe;
|
||||
@@ -1,44 +0,0 @@
|
||||
import { existsSync, rmSync, mkdirSync, readdirSync, writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { render } from '@react-email/render';
|
||||
import decode from 'html-entities-decoder';
|
||||
|
||||
const outputDir = './dist';
|
||||
if (existsSync(outputDir)) {
|
||||
rmSync(outputDir, { recursive: true });
|
||||
}
|
||||
mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
const exportString = 'export default `';
|
||||
|
||||
async function renderEmailTemplates() {
|
||||
const emailsDir = './emails';
|
||||
|
||||
const files = readdirSync(emailsDir);
|
||||
|
||||
for (const file of files) {
|
||||
if (file.endsWith('.tsx')) {
|
||||
try {
|
||||
const templatePath = join(process.cwd(), emailsDir, file);
|
||||
const EmailTemplate = (await import(templatePath)).default;
|
||||
|
||||
const html = await render(EmailTemplate());
|
||||
|
||||
const decodeHtml= decode(html);
|
||||
|
||||
const outputFilename = file.replace('.tsx', '.ts');
|
||||
const outputPath = join(outputDir, outputFilename);
|
||||
|
||||
writeFileSync(outputPath, exportString + decodeHtml + "`");
|
||||
|
||||
console.log(`Rendered ${file} to ${outputPath}`);
|
||||
} catch (error) {
|
||||
console.error(`Error rendering ${file}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderEmailTemplates()
|
||||
.then(() => console.log('Email rendering complete!'))
|
||||
.catch(err => console.error('Error in email rendering process:', err));
|
||||
@@ -1,27 +0,0 @@
|
||||
# React Email Starter
|
||||
|
||||
A live preview right in your browser so you don't need to keep sending real emails during development.
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, install the dependencies:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
# or
|
||||
yarn
|
||||
```
|
||||
|
||||
Then, run the development server:
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "react"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export const baseUrl = 'https://cdn.nocodb.com/emails/v2'
|
||||
@@ -16,6 +16,8 @@ if (!NC_REFRESH_TOKEN_EXP_IN_DAYS || NC_REFRESH_TOKEN_EXP_IN_DAYS <= 0) {
|
||||
throw new Error('NC_REFRESH_TOKEN_EXP_IN_DAYS must be a positive number');
|
||||
}
|
||||
|
||||
export const NC_EMAIL_ASSETS_BASE_URL = 'https://cdn.nocodb.com/emails/v2';
|
||||
|
||||
export const S3_PATCH_KEYS = [
|
||||
'uploads',
|
||||
'thumbnails',
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import * as ejs from 'ejs';
|
||||
import { RoleLabels } from 'nocodb-sdk';
|
||||
import { render } from '@react-email/render';
|
||||
import type { NcRequest } from 'nocodb-sdk';
|
||||
import type { MailParams } from '~/interface/Mail';
|
||||
import type { ComponentProps } from 'react';
|
||||
import * as MailTemplates from '~/services/mail/templates';
|
||||
import { MailEvent } from '~/interface/Mail';
|
||||
import NcPluginMgrv2 from '~/helpers/NcPluginMgrv2';
|
||||
import {
|
||||
BaseInvite,
|
||||
BaseRoleUpdate,
|
||||
OrganizationInvite,
|
||||
OrganizationRoleUpdate,
|
||||
PasswordReset,
|
||||
VerifyEmail,
|
||||
Welcome,
|
||||
} from '~/services/mail/templates';
|
||||
import Noco from '~/Noco';
|
||||
import config from '~/app.config';
|
||||
|
||||
type TemplateComponent<K extends keyof typeof MailTemplates> =
|
||||
(typeof MailTemplates)[K];
|
||||
type TemplateProps<K extends keyof typeof MailTemplates> = ComponentProps<
|
||||
TemplateComponent<K>
|
||||
>;
|
||||
|
||||
@Injectable()
|
||||
export class MailService {
|
||||
async getAdapter() {
|
||||
@@ -28,6 +27,15 @@ export class MailService {
|
||||
}
|
||||
}
|
||||
|
||||
async renderMail<K extends keyof typeof MailTemplates>(
|
||||
template: K,
|
||||
props: TemplateProps<K>,
|
||||
) {
|
||||
const Component = MailTemplates[template];
|
||||
// TODO: Fix Type here
|
||||
return await render(Component(props as TemplateProps<any>));
|
||||
}
|
||||
|
||||
buildUrl(
|
||||
req: NcRequest,
|
||||
params?: {
|
||||
@@ -101,7 +109,7 @@ export class MailService {
|
||||
await mailerAdapter.mailSend({
|
||||
to: user.email,
|
||||
subject: `You have been invited to ${base.title}`,
|
||||
html: ejs.render(BaseInvite, {
|
||||
html: await this.renderMail('BaseInvite', {
|
||||
baseTitle: base.title,
|
||||
name:
|
||||
invitee.display_name ??
|
||||
@@ -122,7 +130,7 @@ export class MailService {
|
||||
await mailerAdapter.mailSend({
|
||||
to: user.email,
|
||||
subject: `Role updated in ${base.title}`,
|
||||
html: ejs.render(BaseRoleUpdate, {
|
||||
html: await this.renderMail('BaseRoleUpdate', {
|
||||
baseTitle: base.title,
|
||||
name:
|
||||
user.display_name ?? user.email.split('@')[0].toLocaleUpperCase(),
|
||||
@@ -142,7 +150,7 @@ export class MailService {
|
||||
await mailerAdapter.mailSend({
|
||||
to: user.email,
|
||||
subject: `Reset your password`,
|
||||
html: ejs.render(PasswordReset, {
|
||||
html: await this.renderMail('PasswordReset', {
|
||||
email: user.email,
|
||||
link: this.buildUrl(req, {
|
||||
token: (user as any).reset_password_token,
|
||||
@@ -157,7 +165,7 @@ export class MailService {
|
||||
await mailerAdapter.mailSend({
|
||||
to: user.email,
|
||||
subject: `Verify your email`,
|
||||
html: ejs.render(VerifyEmail, {
|
||||
html: await this.renderMail('VerifyEmail', {
|
||||
email: user.email,
|
||||
link: this.buildUrl(req, {
|
||||
verificationToken: (user as any).email_verification_token,
|
||||
@@ -171,7 +179,7 @@ export class MailService {
|
||||
await mailerAdapter.mailSend({
|
||||
to: user.email,
|
||||
subject: `Welcome to NocoDB!`,
|
||||
html: ejs.render(Welcome, {
|
||||
html: await this.renderMail('Welcome', {
|
||||
email: user.email,
|
||||
link: this.buildUrl(req, {}),
|
||||
}),
|
||||
@@ -183,7 +191,7 @@ export class MailService {
|
||||
await mailerAdapter.mailSend({
|
||||
to: user.email,
|
||||
subject: `You have been invited to join NocoDB`,
|
||||
html: ejs.render(OrganizationInvite, {
|
||||
html: await this.renderMail('OrganizationInvite', {
|
||||
name:
|
||||
user.display_name ?? user.email.split('@')[0].toLocaleUpperCase(),
|
||||
email: user.email,
|
||||
@@ -199,7 +207,7 @@ export class MailService {
|
||||
await mailerAdapter.mailSend({
|
||||
to: user.email,
|
||||
subject: `Role updated in NocoDB`,
|
||||
html: ejs.render(OrganizationRoleUpdate, {
|
||||
html: await this.renderMail('OrganizationRoleUpdate', {
|
||||
name:
|
||||
user.display_name ?? user.email.split('@')[0].toLocaleUpperCase(),
|
||||
email: user.email,
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Preview,
|
||||
Section,
|
||||
Text,
|
||||
Button, Section,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import RootWrapper from '../components/RootWrapper';
|
||||
import { Footer } from '../components/Footer';
|
||||
import ContentWrapper from '../components/ContentWrapper';
|
||||
import {
|
||||
ContentWrapper,
|
||||
Footer,
|
||||
RootWrapper,
|
||||
} from '~/services/mail/templates/components';
|
||||
|
||||
// Corresponding ejs template
|
||||
interface Props {
|
||||
interface BaseInviteTemplateProps {
|
||||
baseTitle: string;
|
||||
name: string;
|
||||
email: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const BaseInvite = () => (
|
||||
export const BaseInvite = ({
|
||||
baseTitle,
|
||||
name,
|
||||
email,
|
||||
link,
|
||||
}: BaseInviteTemplateProps) => (
|
||||
<Html>
|
||||
<RootWrapper>
|
||||
<Head />
|
||||
@@ -31,22 +38,31 @@ export const BaseInvite = () => (
|
||||
You’ve been invited to a Base
|
||||
</Heading>
|
||||
<Section className="py-6 mx-auto font-bold mx-auto text-center text-gray-900 text-base">
|
||||
{'<%= baseTitle %>'}
|
||||
{baseTitle}
|
||||
</Section>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
<span className="font-bold text-gray-800">{'<%= name %>'}</span> ( {'<%= email %>'}) has invited you to
|
||||
collaborate on <span className="font-bold text-gray-800">{'<%= baseTitle %>'}</span>
|
||||
<span className="font-bold text-gray-800">{name}</span> ({email})
|
||||
has invited you to collaborate on{' '}
|
||||
<span className="font-bold text-gray-800">{baseTitle}</span>
|
||||
</Text>
|
||||
<Button className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10" href="<%= link %>">
|
||||
<Text className="!my-[8px]">
|
||||
Go to Base
|
||||
</Text>
|
||||
<Button
|
||||
className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10"
|
||||
href={link}
|
||||
>
|
||||
<Text className="!my-[8px]">Go to Base</Text>
|
||||
</Button>
|
||||
</ContentWrapper>
|
||||
<Footer />
|
||||
</Body>
|
||||
</RootWrapper>
|
||||
|
||||
</Html>
|
||||
);
|
||||
|
||||
BaseInvite.PreviewProps = {
|
||||
baseTitle: 'Base Title',
|
||||
name: 'John Doe',
|
||||
email: 'johndoe@nocodb.com',
|
||||
link: 'https://app.nocodb.com',
|
||||
};
|
||||
|
||||
export default BaseInvite;
|
||||
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Img,
|
||||
Preview,
|
||||
Section,
|
||||
Text,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
ContentWrapper,
|
||||
Footer,
|
||||
RootWrapper,
|
||||
} from '~/services/mail/templates/components';
|
||||
import { NC_EMAIL_ASSETS_BASE_URL } from '~/constants';
|
||||
|
||||
interface BaseRoleUpdateTemplateProps {
|
||||
baseTitle: string;
|
||||
role: string;
|
||||
name: string;
|
||||
email: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const BaseRoleUpdate = ({
|
||||
baseTitle,
|
||||
email,
|
||||
link,
|
||||
role,
|
||||
name,
|
||||
}: BaseRoleUpdateTemplateProps) => (
|
||||
<Html>
|
||||
<RootWrapper>
|
||||
<Head />
|
||||
<Preview>Your base role has been updated</Preview>
|
||||
<Body className="bg-white">
|
||||
<ContentWrapper>
|
||||
<Heading className="text-gray-900 text-center font-bold m-auto text-xl md:text-2xl">
|
||||
Your base role has been updated
|
||||
</Heading>
|
||||
<Section className="py-6 text-center font-bold text-gray-900 text-base">
|
||||
{baseTitle}
|
||||
</Section>
|
||||
<Section className="pb-6 text-center">
|
||||
<Img
|
||||
src={`${NC_EMAIL_ASSETS_BASE_URL}/badges/${role}.png`}
|
||||
alt={role}
|
||||
className="h-7 mx-auto"
|
||||
/>
|
||||
</Section>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
Your access in
|
||||
<span className="font-bold text-gray-800">{baseTitle}</span>
|
||||
has been updated to{' '}
|
||||
<span className="font-bold text-gray-800 capitalize ">{role}</span>
|
||||
by <span className="font-bold text-gray-800">{` ${name}`}</span> ({' '}
|
||||
{email})
|
||||
</Text>
|
||||
<Button
|
||||
className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10"
|
||||
href={link}
|
||||
>
|
||||
<Text className="!my-[8px]">Go to Base</Text>
|
||||
</Button>
|
||||
</ContentWrapper>
|
||||
<Footer />
|
||||
</Body>
|
||||
</RootWrapper>
|
||||
</Html>
|
||||
);
|
||||
|
||||
BaseRoleUpdate.PreviewProps = {
|
||||
baseTitle: 'Base Title',
|
||||
role: 'editor',
|
||||
name: 'John Doe',
|
||||
email: 'johndoe@nocodb.com',
|
||||
link: 'www.nocodb.com',
|
||||
};
|
||||
|
||||
export default BaseRoleUpdate;
|
||||
@@ -1,16 +1,14 @@
|
||||
import { Img, Section, Container } from '@react-email/components';
|
||||
import { baseUrl } from '../utils/constant';
|
||||
import { Container, Img, Section } from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import { NC_EMAIL_ASSETS_BASE_URL } from '~/constants';
|
||||
|
||||
const ContentWrapper = ({ children }: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
export const ContentWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<Container className="px-3 my-16 max-w-[480px]">
|
||||
<Section className="py-6 m-auto bg-gray-50 border border-gray-200 border-solid rounded-t-xl">
|
||||
<Img
|
||||
alt="NocoDB"
|
||||
src={`${baseUrl}/nocodb-logo.png`}
|
||||
src={`${NC_EMAIL_ASSETS_BASE_URL}/nocodb-logo.png`}
|
||||
width={40}
|
||||
style={{ display: 'block', margin: 'auto auto' }}
|
||||
height={40}
|
||||
@@ -19,9 +17,6 @@ const ContentWrapper = ({ children }: {
|
||||
<Section className="p-6 border border-gray-200 border-solid border-t-0 rounded-b-xl bg-white">
|
||||
{children}
|
||||
</Section>
|
||||
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContentWrapper;
|
||||
);
|
||||
};
|
||||
@@ -1,60 +1,73 @@
|
||||
import { Column, Container, Img, Link, Row, Section, Text } from '@react-email/components';
|
||||
import {
|
||||
Column,
|
||||
Container,
|
||||
Img,
|
||||
Link,
|
||||
Row,
|
||||
Section,
|
||||
Text,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import { baseUrl } from '../utils/constant';
|
||||
|
||||
import { NC_EMAIL_ASSETS_BASE_URL } from '~/constants';
|
||||
export const Footer = () => {
|
||||
return (
|
||||
<Container className="px-3">
|
||||
<Text className="text-gray-500 m-auto text-sm max-w-[400px] text-center">
|
||||
NocoDB is your solution for all your no-code needs.
|
||||
Now on cloud, we help organisations maintain critical data with our solutions.
|
||||
NocoDB is your solution for all your no-code needs. Now on cloud, we
|
||||
help organisations maintain critical data with our solutions.
|
||||
</Text>
|
||||
<Section className="mt-12">
|
||||
<Row className="max-w-[100px] m-auto">
|
||||
<Column>
|
||||
<Link href="https://github.com/nocodb" target="_blank">
|
||||
<Img alt="Github"
|
||||
src={`${baseUrl}/social/github.png`}
|
||||
height={32}
|
||||
width={32}
|
||||
<Img
|
||||
alt="Github"
|
||||
src={`${NC_EMAIL_ASSETS_BASE_URL}/social/github.png`}
|
||||
height={32}
|
||||
width={32}
|
||||
/>
|
||||
</Link>
|
||||
</Column>
|
||||
<Column>
|
||||
<Link href="https://twitter.com/nocodb" target="_blank">
|
||||
|
||||
<Img alt="X"
|
||||
src={`${baseUrl}/social/x.png`}
|
||||
height={32}
|
||||
width={32} />
|
||||
<Img
|
||||
alt="X"
|
||||
src={`${NC_EMAIL_ASSETS_BASE_URL}/social/x.png`}
|
||||
height={32}
|
||||
width={32}
|
||||
/>
|
||||
</Link>
|
||||
|
||||
</Column>
|
||||
<Column>
|
||||
<Link href="https://www.youtube.com/@nocodb" target="_blank">
|
||||
<Img alt="Youtube"
|
||||
height={32}
|
||||
width={32}
|
||||
src={`${baseUrl}/social/youtube.png`}
|
||||
<Img
|
||||
alt="Youtube"
|
||||
height={32}
|
||||
width={32}
|
||||
src={`${NC_EMAIL_ASSETS_BASE_URL}/social/youtube.png`}
|
||||
/>
|
||||
</Link>
|
||||
</Column>
|
||||
<Column>
|
||||
<Link href="http://discord.nocodb.com/" target="_blank">
|
||||
<Img alt="Discord"
|
||||
src={`${baseUrl}/social/discord.png`}
|
||||
height={32}
|
||||
width={32}
|
||||
<Img
|
||||
alt="Discord"
|
||||
src={`${NC_EMAIL_ASSETS_BASE_URL}/social/discord.png`}
|
||||
height={32}
|
||||
width={32}
|
||||
/>
|
||||
</Link>
|
||||
|
||||
</Column>
|
||||
<Column>
|
||||
<Link href="https://www.linkedin.com/company/nocodb" target="_blank">
|
||||
<Img alt="Linkedin"
|
||||
src={`${baseUrl}/social/linkedin.png`}
|
||||
height={32}
|
||||
width={32}
|
||||
<Link
|
||||
href="https://www.linkedin.com/company/nocodb"
|
||||
target="_blank"
|
||||
>
|
||||
<Img
|
||||
alt="Linkedin"
|
||||
src={`${NC_EMAIL_ASSETS_BASE_URL}/social/linkedin.png`}
|
||||
height={32}
|
||||
width={32}
|
||||
/>
|
||||
</Link>
|
||||
</Column>
|
||||
@@ -108,5 +121,5 @@ export const Footer = () => {
|
||||
</Row>
|
||||
</Section>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,7 @@
|
||||
import { Font, Tailwind } from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
|
||||
const RootWrapper = ({children}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
export const RootWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<Tailwind
|
||||
config={{
|
||||
@@ -12,56 +10,56 @@ const RootWrapper = ({children}: {
|
||||
sans: ['Manrope', 'sans-serif'],
|
||||
},
|
||||
fontSize: {
|
||||
xs: ["12px", { lineHeight: "16px" }],
|
||||
sm: ["14px", { lineHeight: "20px" }],
|
||||
base: ["16px", { lineHeight: "24px" }],
|
||||
lg: ["18px", { lineHeight: "28px" }],
|
||||
xl: ["20px", { lineHeight: "28px" }],
|
||||
"2xl": ["24px", { lineHeight: "32px" }],
|
||||
"3xl": ["30px", { lineHeight: "36px" }],
|
||||
"4xl": ["36px", { lineHeight: "36px" }],
|
||||
"5xl": ["48px", { lineHeight: "1" }],
|
||||
"6xl": ["60px", { lineHeight: "1" }],
|
||||
"7xl": ["72px", { lineHeight: "1" }],
|
||||
"8xl": ["96px", { lineHeight: "1" }],
|
||||
"9xl": ["144px", { lineHeight: "1" }],
|
||||
xs: ['12px', { lineHeight: '16px' }],
|
||||
sm: ['14px', { lineHeight: '20px' }],
|
||||
base: ['16px', { lineHeight: '24px' }],
|
||||
lg: ['18px', { lineHeight: '28px' }],
|
||||
xl: ['20px', { lineHeight: '28px' }],
|
||||
'2xl': ['24px', { lineHeight: '32px' }],
|
||||
'3xl': ['30px', { lineHeight: '36px' }],
|
||||
'4xl': ['36px', { lineHeight: '36px' }],
|
||||
'5xl': ['48px', { lineHeight: '1' }],
|
||||
'6xl': ['60px', { lineHeight: '1' }],
|
||||
'7xl': ['72px', { lineHeight: '1' }],
|
||||
'8xl': ['96px', { lineHeight: '1' }],
|
||||
'9xl': ['144px', { lineHeight: '1' }],
|
||||
},
|
||||
spacing: {
|
||||
px: "1px",
|
||||
0: "0",
|
||||
0.5: "2px",
|
||||
1: "4px",
|
||||
1.5: "6px",
|
||||
2: "8px",
|
||||
2.5: "10px",
|
||||
3: "12px",
|
||||
3.5: "14px",
|
||||
4: "16px",
|
||||
5: "20px",
|
||||
6: "24px",
|
||||
7: "28px",
|
||||
8: "32px",
|
||||
9: "36px",
|
||||
10: "40px",
|
||||
11: "44px",
|
||||
12: "48px",
|
||||
14: "56px",
|
||||
16: "64px",
|
||||
20: "80px",
|
||||
24: "96px",
|
||||
28: "112px",
|
||||
32: "128px",
|
||||
36: "144px",
|
||||
40: "160px",
|
||||
44: "176px",
|
||||
48: "192px",
|
||||
52: "208px",
|
||||
56: "224px",
|
||||
60: "240px",
|
||||
64: "256px",
|
||||
72: "288px",
|
||||
80: "320px",
|
||||
96: "384px",
|
||||
px: '1px',
|
||||
0: '0',
|
||||
0.5: '2px',
|
||||
1: '4px',
|
||||
1.5: '6px',
|
||||
2: '8px',
|
||||
2.5: '10px',
|
||||
3: '12px',
|
||||
3.5: '14px',
|
||||
4: '16px',
|
||||
5: '20px',
|
||||
6: '24px',
|
||||
7: '28px',
|
||||
8: '32px',
|
||||
9: '36px',
|
||||
10: '40px',
|
||||
11: '44px',
|
||||
12: '48px',
|
||||
14: '56px',
|
||||
16: '64px',
|
||||
20: '80px',
|
||||
24: '96px',
|
||||
28: '112px',
|
||||
32: '128px',
|
||||
36: '144px',
|
||||
40: '160px',
|
||||
44: '176px',
|
||||
48: '192px',
|
||||
52: '208px',
|
||||
56: '224px',
|
||||
60: '240px',
|
||||
64: '256px',
|
||||
72: '288px',
|
||||
80: '320px',
|
||||
96: '384px',
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
@@ -207,5 +205,3 @@ const RootWrapper = ({children}: {
|
||||
</Tailwind>
|
||||
);
|
||||
};
|
||||
|
||||
export default RootWrapper;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ContentWrapper } from '~/services/mail/templates/components/ContentWrapper';
|
||||
import { Footer } from '~/services/mail/templates/components/Footer';
|
||||
import { RootWrapper } from '~/services/mail/templates/components/RootWrapper';
|
||||
|
||||
export { ContentWrapper, Footer, RootWrapper };
|
||||
@@ -1,10 +1,10 @@
|
||||
import Welcome from './welcome';
|
||||
import BaseInvite from './base-invite';
|
||||
import BaseRoleUpdate from './base-role-update';
|
||||
import PasswordReset from './password-reset';
|
||||
import VerifyEmail from './verify-your-email';
|
||||
import OrganizationInvite from './org-invite';
|
||||
import OrganizationRoleUpdate from './org-role-update';
|
||||
import Welcome from '~/services/mail/templates/welcome';
|
||||
import BaseInvite from '~/services/mail/templates/base-invite';
|
||||
import PasswordReset from '~/services/mail/templates/password-reset';
|
||||
import VerifyEmail from '~/services/mail/templates/verify-your-email';
|
||||
import OrganizationInvite from '~/services/mail/templates/org-invite';
|
||||
import OrganizationRoleUpdate from '~/services/mail/templates/org-role-update';
|
||||
import BaseRoleUpdate from '~/services/mail/templates/base-role-update';
|
||||
|
||||
export {
|
||||
Welcome,
|
||||
|
||||
60
packages/nocodb/src/services/mail/templates/org-invite.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Preview,
|
||||
Text,
|
||||
} from '@react-email/components';
|
||||
import {
|
||||
ContentWrapper,
|
||||
Footer,
|
||||
RootWrapper,
|
||||
} from '~/services/mail/templates/components';
|
||||
|
||||
interface OrganizationInviteTemplateProps {
|
||||
name: string;
|
||||
email: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const OrganizationInvite = ({
|
||||
name,
|
||||
email,
|
||||
link,
|
||||
}: OrganizationInviteTemplateProps) => (
|
||||
<Html>
|
||||
<RootWrapper>
|
||||
<Head />
|
||||
<Preview>You’ve been invited to NocoDB</Preview>
|
||||
<Body className="bg-white">
|
||||
<ContentWrapper>
|
||||
<Heading className="text-gray-900 text-center font-bold m-auto text-xl md:text-2xl">
|
||||
You’ve been invited to NocoDB
|
||||
</Heading>
|
||||
<Text className="text-gray-600 text-center text-sm">
|
||||
<span className="font-bold text-gray-800">{name}</span> ( {email})
|
||||
has invited you to collaborate on NocoDB.
|
||||
</Text>
|
||||
<Button
|
||||
className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10"
|
||||
href={link}
|
||||
>
|
||||
<Text className="!my-[8px]">Go to NocoDB</Text>
|
||||
</Button>
|
||||
</ContentWrapper>
|
||||
<Footer />
|
||||
</Body>
|
||||
</RootWrapper>
|
||||
</Html>
|
||||
);
|
||||
|
||||
OrganizationInvite.PreviewProps = {
|
||||
name: 'John Doe',
|
||||
email: 'johndoe@gmail.com',
|
||||
link: 'https://nocodb.com',
|
||||
};
|
||||
|
||||
export default OrganizationInvite;
|
||||
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Img,
|
||||
Preview,
|
||||
Section,
|
||||
Text,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import { NC_EMAIL_ASSETS_BASE_URL } from '~/constants';
|
||||
import {
|
||||
ContentWrapper,
|
||||
Footer,
|
||||
RootWrapper,
|
||||
} from '~/services/mail/templates/components';
|
||||
|
||||
interface OrganizationRoleUpdateTemplateProps {
|
||||
role: string;
|
||||
name: string;
|
||||
email: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const OrganizationRoleUpdate = ({
|
||||
role,
|
||||
email,
|
||||
name,
|
||||
link,
|
||||
}: OrganizationRoleUpdateTemplateProps) => (
|
||||
<Html>
|
||||
<RootWrapper>
|
||||
<Head />
|
||||
<Preview>Your organization role has been updated</Preview>
|
||||
<Body className="bg-white">
|
||||
<ContentWrapper>
|
||||
<Heading className="text-gray-900 text-center font-bold m-auto text-xl md:text-2xl">
|
||||
Your organization role has been updated
|
||||
</Heading>
|
||||
<Section className="py-6 text-center">
|
||||
<Img
|
||||
src={`${NC_EMAIL_ASSETS_BASE_URL}/badges/${role}.png`}
|
||||
alt="<%= role %>"
|
||||
className="h-7 mx-auto"
|
||||
/>
|
||||
</Section>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
Your access in NocoDB has been updated to
|
||||
<span className="font-bold text-gray-800 capitalize"> {role} </span>
|
||||
by <span className="font-bold text-gray-800">{name}</span> ({email})
|
||||
</Text>
|
||||
<Button
|
||||
className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10"
|
||||
href={link}
|
||||
>
|
||||
<Text className="!my-[8px]">Go to NocoDB</Text>
|
||||
</Button>
|
||||
</ContentWrapper>
|
||||
<Footer />
|
||||
</Body>
|
||||
</RootWrapper>
|
||||
</Html>
|
||||
);
|
||||
|
||||
OrganizationRoleUpdate.PreviewProps = {
|
||||
role: 'creator',
|
||||
email: 'janedoe@nocodb.com',
|
||||
name: 'Jane Doe',
|
||||
link: 'https://nocodb.com',
|
||||
};
|
||||
|
||||
export default OrganizationRoleUpdate;
|
||||
@@ -1,24 +1,26 @@
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Preview,
|
||||
Section,
|
||||
Text,
|
||||
Button, Section,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import RootWrapper from '../components/RootWrapper';
|
||||
import { Footer } from '../components/Footer';
|
||||
import ContentWrapper from '../components/ContentWrapper';
|
||||
import {
|
||||
ContentWrapper,
|
||||
Footer,
|
||||
RootWrapper,
|
||||
} from '~/services/mail/templates/components';
|
||||
|
||||
// Corresponding ejs template
|
||||
interface Props {
|
||||
interface PasswordResetTemplateProps {
|
||||
email: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const PasswordReset = () => (
|
||||
export const PasswordReset = ({ email, link }: PasswordResetTemplateProps) => (
|
||||
<Html>
|
||||
<RootWrapper>
|
||||
<Head />
|
||||
@@ -29,15 +31,17 @@ export const PasswordReset = () => (
|
||||
Password reset requested
|
||||
</Heading>
|
||||
<Section className="py-6 mx-auto font-bold text-center text-gray-900 text-base">
|
||||
{'<%= email %>'}
|
||||
{email}
|
||||
</Section>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
You’ve requested for a password reset, click on the ‘Reset Password’ button to reset your password.
|
||||
You’ve requested for a password reset, click on the ‘Reset Password’
|
||||
button to reset your password.
|
||||
</Text>
|
||||
<Button className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10" href="<%= link %>">
|
||||
<Text className="!my-[8px]">
|
||||
Reset Password
|
||||
</Text>
|
||||
<Button
|
||||
className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10"
|
||||
href={link}
|
||||
>
|
||||
<Text className="!my-[8px]">Reset Password</Text>
|
||||
</Button>
|
||||
</ContentWrapper>
|
||||
<Footer />
|
||||
@@ -45,4 +49,10 @@ export const PasswordReset = () => (
|
||||
</RootWrapper>
|
||||
</Html>
|
||||
);
|
||||
|
||||
PasswordReset.PreviewProps = {
|
||||
email: 'janedoe@nocodb.com',
|
||||
link: 'https://nocodb.com',
|
||||
};
|
||||
|
||||
export default PasswordReset;
|
||||
@@ -1,24 +1,29 @@
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Preview,
|
||||
Section,
|
||||
Text,
|
||||
Button, Section,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import RootWrapper from '../components/RootWrapper';
|
||||
import { Footer } from '../components/Footer';
|
||||
import ContentWrapper from '../components/ContentWrapper';
|
||||
import {
|
||||
ContentWrapper,
|
||||
Footer,
|
||||
RootWrapper,
|
||||
} from '~/services/mail/templates/components';
|
||||
|
||||
// Corresponding ejs template
|
||||
interface Props {
|
||||
interface VerifyYourEmailTemplateProps {
|
||||
email: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const VerifyYourEmail = () => (
|
||||
export const VerifyYourEmail = ({
|
||||
email,
|
||||
link,
|
||||
}: VerifyYourEmailTemplateProps) => (
|
||||
<Html>
|
||||
<RootWrapper>
|
||||
<Head />
|
||||
@@ -29,15 +34,16 @@ export const VerifyYourEmail = () => (
|
||||
Verify your Email
|
||||
</Heading>
|
||||
<Section className="py-6 mx-auto font-bold text-center text-gray-900 text-base">
|
||||
{'<%= email %>'}
|
||||
{email}
|
||||
</Section>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
Please verify your account to complete the sign-up process.
|
||||
</Text>
|
||||
<Button className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10" href="<%= link %>">
|
||||
<Text className="!my-[8px]">
|
||||
Verify Email
|
||||
</Text>
|
||||
<Button
|
||||
className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10"
|
||||
href={link}
|
||||
>
|
||||
<Text className="!my-[8px]">Verify Email</Text>
|
||||
</Button>
|
||||
</ContentWrapper>
|
||||
<Footer />
|
||||
@@ -45,4 +51,10 @@ export const VerifyYourEmail = () => (
|
||||
</RootWrapper>
|
||||
</Html>
|
||||
);
|
||||
|
||||
VerifyYourEmail.PreviewProps = {
|
||||
email: 'janedoe@gmail.com',
|
||||
link: 'https://nocodb.com',
|
||||
};
|
||||
|
||||
export default VerifyYourEmail;
|
||||
@@ -1,24 +1,26 @@
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Preview,
|
||||
Section,
|
||||
Text,
|
||||
Button, Section,
|
||||
} from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
import RootWrapper from '../components/RootWrapper';
|
||||
import { Footer } from '../components/Footer';
|
||||
import ContentWrapper from '../components/ContentWrapper';
|
||||
import {
|
||||
ContentWrapper,
|
||||
Footer,
|
||||
RootWrapper,
|
||||
} from '~/services/mail/templates/components';
|
||||
|
||||
// Corresponding ejs template
|
||||
interface Props {
|
||||
interface WelcomeTemplateProps {
|
||||
email: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const Welcome = () => (
|
||||
export const Welcome = ({ email, link }: WelcomeTemplateProps) => (
|
||||
<Html>
|
||||
<RootWrapper>
|
||||
<Head />
|
||||
@@ -29,13 +31,16 @@ export const Welcome = () => (
|
||||
Welcome to NocoDB!
|
||||
</Heading>
|
||||
<Section className="py-6 mx-auto font-bold text-center text-gray-900 text-base">
|
||||
{'<%= email %>'}
|
||||
{email}
|
||||
</Section>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
We're thrilled to have you on board! 🚀 Turn your databases into powerful smart tables and manage your data the way you want — no code required.
|
||||
We're thrilled to have you on board! 🚀 Turn your databases into
|
||||
powerful smart tables and manage your data the way you want — no
|
||||
code required.
|
||||
</Text>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
Get started by creating your first project or exploring templates to see what’s possible.
|
||||
Get started by creating your first project or exploring templates to
|
||||
see what’s possible.
|
||||
</Text>
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
Need help? Our docs and community are just a click away.
|
||||
@@ -43,10 +48,11 @@ export const Welcome = () => (
|
||||
<Text className="text-gray-600 text-center text-sm !mt-0">
|
||||
Let’s build something amazing together! 💡
|
||||
</Text>
|
||||
<Button className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10" href="<%= link %>">
|
||||
<Text className="!my-[8px]">
|
||||
Go to your Workspace
|
||||
</Text>
|
||||
<Button
|
||||
className="text-center w-full text-base font-bold bg-brand-500 text-white rounded-lg h-10"
|
||||
href={link}
|
||||
>
|
||||
<Text className="!my-[8px]">Go to your Workspace</Text>
|
||||
</Button>
|
||||
</ContentWrapper>
|
||||
<Footer />
|
||||
@@ -54,4 +60,10 @@ export const Welcome = () => (
|
||||
</RootWrapper>
|
||||
</Html>
|
||||
);
|
||||
|
||||
Welcome.PreviewProps = {
|
||||
email: 'janedoe@nocodb.com',
|
||||
link: 'https://nocodb.com',
|
||||
};
|
||||
|
||||
export default Welcome;
|
||||
@@ -20,6 +20,7 @@
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react",
|
||||
"paths": {
|
||||
"src/*": [
|
||||
"src/*"
|
||||
|
||||