promote-email-templates 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/.react-email/.eslintrc.js +52 -0
  2. package/.react-email/.prettierignore +3 -0
  3. package/.react-email/.prettierrc.js +8 -0
  4. package/.react-email/license.md +7 -0
  5. package/.react-email/next.config.js +36 -0
  6. package/.react-email/package.json +1 -0
  7. package/.react-email/postcss.config.js +8 -0
  8. package/.react-email/readme.md +44 -0
  9. package/.react-email/src/actions/get-email-path-from-slug.ts +26 -0
  10. package/.react-email/src/actions/get-emails-directory-metadata.spec.ts +73 -0
  11. package/.react-email/src/actions/get-emails-directory-metadata.ts +91 -0
  12. package/.react-email/src/actions/render-email-by-path.tsx +59 -0
  13. package/.react-email/src/app/favicon.ico +0 -0
  14. package/.react-email/src/app/globals.css +35 -0
  15. package/.react-email/src/app/inter.ts +7 -0
  16. package/.react-email/src/app/layout.tsx +36 -0
  17. package/.react-email/src/app/logo.png +0 -0
  18. package/.react-email/src/app/page.tsx +47 -0
  19. package/.react-email/src/app/preview/[...slug]/page.tsx +65 -0
  20. package/.react-email/src/app/preview/[...slug]/preview.tsx +141 -0
  21. package/.react-email/src/app/preview/[...slug]/rendering-error.tsx +40 -0
  22. package/.react-email/src/components/button.tsx +90 -0
  23. package/.react-email/src/components/code-container.tsx +145 -0
  24. package/.react-email/src/components/code.tsx +112 -0
  25. package/.react-email/src/components/heading.tsx +113 -0
  26. package/.react-email/src/components/icons/icon-arrow-down.tsx +16 -0
  27. package/.react-email/src/components/icons/icon-base.tsx +24 -0
  28. package/.react-email/src/components/icons/icon-button.tsx +23 -0
  29. package/.react-email/src/components/icons/icon-check.tsx +19 -0
  30. package/.react-email/src/components/icons/icon-clipboard.tsx +40 -0
  31. package/.react-email/src/components/icons/icon-download.tsx +19 -0
  32. package/.react-email/src/components/icons/icon-file.tsx +19 -0
  33. package/.react-email/src/components/icons/icon-folder-open.tsx +19 -0
  34. package/.react-email/src/components/icons/icon-folder.tsx +18 -0
  35. package/.react-email/src/components/icons/icon-hide-sidebar.tsx +23 -0
  36. package/.react-email/src/components/icons/icon-monitor.tsx +19 -0
  37. package/.react-email/src/components/icons/icon-phone.tsx +26 -0
  38. package/.react-email/src/components/icons/icon-source.tsx +19 -0
  39. package/.react-email/src/components/index.ts +7 -0
  40. package/.react-email/src/components/logo.tsx +64 -0
  41. package/.react-email/src/components/send.tsx +135 -0
  42. package/.react-email/src/components/shell.tsx +115 -0
  43. package/.react-email/src/components/sidebar/index.ts +1 -0
  44. package/.react-email/src/components/sidebar/sidebar-directory-children.tsx +134 -0
  45. package/.react-email/src/components/sidebar/sidebar-directory.tsx +106 -0
  46. package/.react-email/src/components/sidebar/sidebar.tsx +45 -0
  47. package/.react-email/src/components/text.tsx +99 -0
  48. package/.react-email/src/components/tooltip-content.tsx +32 -0
  49. package/.react-email/src/components/tooltip.tsx +19 -0
  50. package/.react-email/src/components/topbar.tsx +161 -0
  51. package/.react-email/src/contexts/emails.tsx +127 -0
  52. package/.react-email/src/hooks/use-hot-reload.ts +35 -0
  53. package/.react-email/src/hooks/use-rendering-metadata.ts +36 -0
  54. package/.react-email/src/utils/cn.ts +6 -0
  55. package/.react-email/src/utils/constants.ts +6 -0
  56. package/.react-email/src/utils/copy-text-to-clipboard.ts +7 -0
  57. package/.react-email/src/utils/emails-directory-absolute-path.ts +34 -0
  58. package/.react-email/src/utils/get-email-component.ts +108 -0
  59. package/.react-email/src/utils/improve-error-with-sourcemap.ts +55 -0
  60. package/.react-email/src/utils/index.ts +5 -0
  61. package/.react-email/src/utils/language-map.ts +7 -0
  62. package/.react-email/src/utils/static-node-modules-for-vm.ts +92 -0
  63. package/.react-email/src/utils/types/as.ts +26 -0
  64. package/.react-email/src/utils/types/email-template.ts +8 -0
  65. package/.react-email/src/utils/types/error-object.ts +11 -0
  66. package/.react-email/src/utils/types/hot-reload-change.ts +6 -0
  67. package/.react-email/src/utils/types/hot-reload-event.ts +6 -0
  68. package/.react-email/src/utils/unreachable.ts +8 -0
  69. package/.react-email/tailwind.config.ts +94 -0
  70. package/dist/index.d.mts +208 -82
  71. package/dist/index.d.ts +208 -82
  72. package/dist/index.js +292 -91
  73. package/dist/index.mjs +289 -91
  74. package/package.json +1 -1
@@ -0,0 +1,52 @@
1
+ const { resolve } = require('node:path');
2
+
3
+ const project = resolve(process.cwd(), './tsconfig.json');
4
+
5
+ /** @type {import('eslint').ESLint.ConfigData} */
6
+ module.exports = {
7
+ extends: [
8
+ "@vercel/style-guide/eslint/node",
9
+ "@vercel/style-guide/eslint/browser",
10
+ "@vercel/style-guide/eslint/typescript",
11
+ "@vercel/style-guide/eslint/react",
12
+ "@vercel/style-guide/eslint/next",
13
+ "eslint-config-turbo",
14
+ ]
15
+ .map(require.resolve)
16
+ .concat(["eslint-config-prettier"]),
17
+ parserOptions: {
18
+ project,
19
+ },
20
+ globals: {
21
+ React: true,
22
+ JSX: true,
23
+ },
24
+ settings: {
25
+ "import/resolver": {
26
+ typescript: {
27
+ project,
28
+ },
29
+ },
30
+ },
31
+ ignorePatterns: ['cli/', 'cli/index.mjs', "node_modules/", "dist/"],
32
+ rules: {
33
+ "@next/next/no-img-element": "off",
34
+ "@typescript-eslint/explicit-function-return-type": "off",
35
+ "import/no-default-export": "off",
36
+ "jsx-a11y/no-autofocus": "off",
37
+ "no-alert": "off",
38
+ "react/no-array-index-key": "off",
39
+ "react/function-component-definition": [
40
+ 2,
41
+ {
42
+ namedComponents: "arrow-function",
43
+ unnamedComponents: "arrow-function",
44
+ },
45
+ ],
46
+ 'import/no-cycle': 'off',
47
+ 'import/no-extraneous-dependencies': 'off',
48
+ 'turbo/no-undeclared-env-vars': 'off',
49
+ 'eslint-comments/require-description': 'off',
50
+ 'no-console': 'off',
51
+ },
52
+ };
@@ -0,0 +1,3 @@
1
+ .react-email
2
+ dist
3
+ preview/.next
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ quoteProps: 'consistent',
3
+ singleQuote: true,
4
+ trailingComma: 'all',
5
+ printWidth: 80,
6
+ useTabs: false,
7
+ bracketSpacing: true,
8
+ };
@@ -0,0 +1,7 @@
1
+ Copyright 2024 Plus Five Five, Inc
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,36 @@
1
+
2
+ const path = require('path');
3
+ /** @type {import('next').NextConfig} */
4
+ module.exports = {
5
+ env: {
6
+ ...{"NEXT_PUBLIC_EMAILS_DIR_RELATIVE_PATH":"./emails","NEXT_PUBLIC_CLI_PACKAGE_LOCATION":"PLACEHOLDER","NEXT_PUBLIC_OS_PATH_SEPARATOR":"/","NEXT_PUBLIC_USER_PROJECT_LOCATION":"PLACEHOLDER","NEXT_PUBLIC_IS_BUILDING":"true"},
7
+ NEXT_PUBLIC_USER_PROJECT_LOCATION: path.resolve(process.cwd(), '../'),
8
+ NEXT_PUBLIC_CLI_PACKAGE_LOCATION: process.cwd(),
9
+ },
10
+ // this is needed so that the code for building emails works properly
11
+ webpack: (
12
+ /** @type {import('webpack').Configuration & { externals: string[] }} */
13
+ config,
14
+ { isServer }
15
+ ) => {
16
+ if (isServer) {
17
+ config.externals.push('esbuild');
18
+ }
19
+
20
+ return config;
21
+ },
22
+ typescript: {
23
+ ignoreBuildErrors: true
24
+ },
25
+ eslint: {
26
+ ignoreDuringBuilds: true
27
+ },
28
+ experimental: {
29
+ webpackBuildWorker: true,
30
+ serverComponentsExternalPackages: [
31
+ '@react-email/components',
32
+ '@react-email/render',
33
+ '@react-email/tailwind',
34
+ ],
35
+ },
36
+ }
@@ -0,0 +1 @@
1
+ {"name":"react-email","version":"2.1.0","description":"A live preview of your emails right in your browser.","bin":{"email":"./cli/index.js"},"license":"MIT","repository":{"type":"git","url":"https://github.com/resend/react-email.git","directory":"packages/react-email"},"keywords":["react","email"],"engines":{"node":">=18.0.0"},"dependencies":{"@radix-ui/colors":"1.0.1","@radix-ui/react-collapsible":"1.0.3","@radix-ui/react-popover":"1.0.6","@radix-ui/react-slot":"1.0.2","@radix-ui/react-toggle-group":"1.0.4","@radix-ui/react-tooltip":"1.0.6","@react-email/components":"0.0.15","@react-email/render":"0.0.12","@swc/core":"1.3.101","@types/react":"^18.2.0","@types/react-dom":"^18.2.0","@types/webpack":"5.28.5","autoprefixer":"10.4.14","chalk":"4.1.2","chokidar":"3.5.3","clsx":"2.1.0","commander":"11.1.0","debounce":"2.0.0","esbuild":"0.19.11","eslint-config-prettier":"9.0.0","eslint-config-turbo":"1.10.12","framer-motion":"10.17.4","glob":"10.3.4","log-symbols":"4.1.0","mime-types":"2.1.35","next":"14.1.0","normalize-path":"3.0.0","ora":"5.4.1","postcss":"8.4.35","prism-react-renderer":"2.1.0","react":"^18.2.0","react-dom":"^18.2.0","shelljs":"0.8.5","socket.io":"4.7.3","socket.io-client":"4.7.3","sonner":"1.3.1","source-map-js":"1.0.2","stacktrace-parser":"0.1.10","tailwind-merge":"2.2.0","tailwindcss":"3.4.0","tree-cli":"0.6.7","typescript":"5.1.6","sharp":"0.33.2"},"devDependencies":{"@types/fs-extra":"11.0.1","@types/mime-types":"2.1.4","@types/node":"18.0.0","@types/normalize-path":"3.0.2","@types/shelljs":"0.8.15","@vercel/style-guide":"5.1.0","eslint":"8.50.0","tsup":"7.2.0","tsx":"4.7.0","vitest":"1.1.3","watch":"1.0.2"},"scripts":{"build":"next build","dev":"tsup --watch","test":"vitest run","clean":"rm -rf dist","lint":"eslint . && tsc","start":"next start"}}
@@ -0,0 +1,8 @@
1
+ const path = require('node:path');
2
+
3
+ module.exports = {
4
+ plugins: {
5
+ tailwindcss: { config: path.resolve(__dirname, 'tailwind.config.ts') },
6
+ autoprefixer: {},
7
+ },
8
+ }
@@ -0,0 +1,44 @@
1
+ ![React email cover](https://react.email/static/covers/react-email.png)
2
+
3
+ <div align="center"><strong>React Email</strong></div>
4
+ <div align="center">The next generation of writing emails.<br />High-quality, unstyled components for creating emails.</div>
5
+ <br />
6
+ <div align="center">
7
+ <a href="https://react.email">Website</a>
8
+ <span> · </span>
9
+ <a href="https://github.com/resend/react-email">GitHub</a>
10
+ <span> · </span>
11
+ <a href="https://react.email/discord">Discord</a>
12
+ </div>
13
+
14
+ ## Getting started
15
+
16
+ To get started, open a new shell and run:
17
+
18
+ ```sh
19
+ npx create-email
20
+ ```
21
+
22
+ This will create a new folder called `emails` with a few email templates.
23
+
24
+ ## Commands
25
+
26
+ ### `email dev`
27
+
28
+ Starts a local development server that will watch your files and automatically rebuild your email when you make changes.
29
+
30
+ ```sh
31
+ npx react-email dev
32
+ ```
33
+
34
+ ### `email export`
35
+
36
+ Generates the plain HTML files of your emails into a `out` directory.
37
+
38
+ ```sh
39
+ npx react-email export
40
+ ```
41
+
42
+ ## License
43
+
44
+ MIT License
@@ -0,0 +1,26 @@
1
+ 'use server';
2
+ import path from 'node:path';
3
+ import fs from 'node:fs';
4
+ import { emailsDirectoryAbsolutePath } from '../utils/emails-directory-absolute-path';
5
+
6
+ // eslint-disable-next-line @typescript-eslint/require-await
7
+ export const getEmailPathFromSlug = async (slug: string) => {
8
+ if (['.tsx', '.jsx', '.js'].includes(path.extname(slug)))
9
+ return path.join(emailsDirectoryAbsolutePath, slug);
10
+
11
+ const pathWithoutExtension = path.join(emailsDirectoryAbsolutePath, slug);
12
+
13
+ if (fs.existsSync(`${pathWithoutExtension}.tsx`)) {
14
+ return `${pathWithoutExtension}.tsx`;
15
+ } else if (fs.existsSync(`${pathWithoutExtension}.jsx`)) {
16
+ return `${pathWithoutExtension}.jsx`;
17
+ } else if (fs.existsSync(`${pathWithoutExtension}.js`)) {
18
+ return `${pathWithoutExtension}.jsx`;
19
+ }
20
+
21
+ throw new Error(
22
+ `Could not find your email file based on the slug by guessing the file extension. Tried .tsx, .jsx and .js.
23
+
24
+ This is most likely not an issue with the preview server. It most likely is that the email doesn't exist.`,
25
+ );
26
+ };
@@ -0,0 +1,73 @@
1
+ import path from 'node:path';
2
+ import { getEmailsDirectoryMetadata } from './get-emails-directory-metadata';
3
+
4
+ test('getEmailsDirectoryMetadata on demo emails', async () => {
5
+ const emailsDirectoryPath = path.resolve(
6
+ __dirname,
7
+ '../../../../apps/demo/emails/',
8
+ );
9
+ expect(await getEmailsDirectoryMetadata(emailsDirectoryPath)).toEqual({
10
+ absolutePath: emailsDirectoryPath,
11
+ directoryName: 'emails',
12
+ emailFilenames: [],
13
+ subDirectories: [
14
+ {
15
+ absolutePath: `${emailsDirectoryPath}/magic-links`,
16
+ directoryName: 'magic-links',
17
+ emailFilenames: [
18
+ 'aws-verify-email',
19
+ 'linear-login-code',
20
+ 'notion-magic-link',
21
+ 'plaid-verify-identity',
22
+ 'raycast-magic-link',
23
+ 'slack-confirm',
24
+ ],
25
+ subDirectories: [],
26
+ },
27
+ {
28
+ absolutePath: `${emailsDirectoryPath}/newsletters`,
29
+ directoryName: 'newsletters',
30
+ emailFilenames: [
31
+ 'codepen-challengers',
32
+ 'google-play-policy-update',
33
+ 'stack-overflow-tips',
34
+ ],
35
+ subDirectories: [],
36
+ },
37
+ {
38
+ absolutePath: `${emailsDirectoryPath}/notifications`,
39
+ directoryName: 'notifications',
40
+ emailFilenames: [
41
+ 'github-access-token',
42
+ 'vercel-invite-user',
43
+ 'yelp-recent-login',
44
+ ],
45
+ subDirectories: [],
46
+ },
47
+ {
48
+ absolutePath: `${emailsDirectoryPath}/receipts`,
49
+ directoryName: 'receipts',
50
+ emailFilenames: ['apple-receipt', 'nike-receipt'],
51
+ subDirectories: [],
52
+ },
53
+ {
54
+ absolutePath: `${emailsDirectoryPath}/reset-password`,
55
+ directoryName: 'reset-password',
56
+ emailFilenames: ['dropbox-reset-password', 'twitch-reset-password'],
57
+ subDirectories: [],
58
+ },
59
+ {
60
+ absolutePath: `${emailsDirectoryPath}/reviews`,
61
+ directoryName: 'reviews',
62
+ emailFilenames: ['airbnb-review', 'amazon-review'],
63
+ subDirectories: [],
64
+ },
65
+ {
66
+ absolutePath: `${emailsDirectoryPath}/welcome`,
67
+ directoryName: 'welcome',
68
+ emailFilenames: ['koala-welcome', 'netlify-welcome', 'stripe-welcome'],
69
+ subDirectories: [],
70
+ },
71
+ ],
72
+ });
73
+ });
@@ -0,0 +1,91 @@
1
+ 'use server';
2
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+
6
+ const isFileAnEmail = (fullPath: string): boolean => {
7
+ const stat = fs.statSync(fullPath);
8
+
9
+ if (stat.isDirectory()) return false;
10
+
11
+ const { ext } = path.parse(fullPath);
12
+
13
+ if (!['.js', '.tsx', '.jsx'].includes(ext)) return false;
14
+
15
+ // check with a heuristic to see if the file has at least
16
+ // a default export
17
+ const fileContents = fs.readFileSync(fullPath, 'utf8');
18
+
19
+ return /\bexport\s+default\b/gm.test(fileContents);
20
+ };
21
+
22
+ export interface EmailsDirectory {
23
+ absolutePath: string;
24
+ directoryName: string;
25
+ emailFilenames: string[];
26
+ subDirectories: EmailsDirectory[];
27
+ }
28
+
29
+ const mergeDirectoriesWithSubDirectories = (
30
+ emailsDirectoryMetadata: EmailsDirectory,
31
+ ): EmailsDirectory => {
32
+ let currentResultingMergedDirectory: EmailsDirectory =
33
+ emailsDirectoryMetadata;
34
+
35
+ while (
36
+ currentResultingMergedDirectory.emailFilenames.length === 0 &&
37
+ currentResultingMergedDirectory.subDirectories.length === 1
38
+ ) {
39
+ const onlySubDirectory = currentResultingMergedDirectory.subDirectories[0]!;
40
+ currentResultingMergedDirectory = {
41
+ subDirectories: onlySubDirectory.subDirectories,
42
+ emailFilenames: onlySubDirectory.emailFilenames,
43
+ absolutePath: onlySubDirectory.absolutePath,
44
+ directoryName: path.join(
45
+ currentResultingMergedDirectory.directoryName,
46
+ onlySubDirectory.directoryName,
47
+ ),
48
+ };
49
+ }
50
+
51
+ return currentResultingMergedDirectory;
52
+ };
53
+
54
+ export const getEmailsDirectoryMetadata = async (
55
+ absolutePathToEmailsDirectory: string,
56
+ ): Promise<EmailsDirectory | undefined> => {
57
+ if (!fs.existsSync(absolutePathToEmailsDirectory)) return;
58
+
59
+ const dirents = await fs.promises.readdir(absolutePathToEmailsDirectory, {
60
+ withFileTypes: true,
61
+ });
62
+
63
+ const emailFilenames = dirents
64
+ .filter((dirent) =>
65
+ isFileAnEmail(path.join(absolutePathToEmailsDirectory, dirent.name)),
66
+ )
67
+ .map((dirent) => dirent.name.replace(path.extname(dirent.name), ''));
68
+
69
+ const subDirectories = await Promise.all(
70
+ dirents
71
+ .filter(
72
+ (dirent) =>
73
+ dirent.isDirectory() &&
74
+ !dirent.name.startsWith('_') &&
75
+ dirent.name !== 'static',
76
+ )
77
+ .map(
78
+ (dirent) =>
79
+ getEmailsDirectoryMetadata(
80
+ path.join(absolutePathToEmailsDirectory, dirent.name),
81
+ ) as Promise<EmailsDirectory>,
82
+ ),
83
+ );
84
+
85
+ return mergeDirectoriesWithSubDirectories({
86
+ absolutePath: absolutePathToEmailsDirectory,
87
+ directoryName: absolutePathToEmailsDirectory.split(path.sep).pop()!,
88
+ emailFilenames,
89
+ subDirectories,
90
+ });
91
+ };
@@ -0,0 +1,59 @@
1
+ 'use server';
2
+ import fs from 'node:fs';
3
+ import { renderAsync } from '@react-email/render';
4
+ import { getEmailComponent } from '../utils/get-email-component';
5
+ import type { ErrorObject } from '../utils/types/error-object';
6
+ import { improveErrorWithSourceMap } from '../utils/improve-error-with-sourcemap';
7
+
8
+ export interface RenderedEmailMetadata {
9
+ markup: string;
10
+ plainText: string;
11
+ reactMarkup: string;
12
+ }
13
+
14
+ export type EmailRenderingResult =
15
+ | RenderedEmailMetadata
16
+ | {
17
+ error: ErrorObject;
18
+ };
19
+
20
+ export const renderEmailByPath = async (
21
+ emailPath: string,
22
+ ): Promise<EmailRenderingResult> => {
23
+ const result = await getEmailComponent(emailPath);
24
+
25
+ if ('error' in result) {
26
+ return { error: result.error };
27
+ }
28
+
29
+ const { emailComponent: Email, sourceMapToOriginalFile } = result;
30
+
31
+ const previewProps = Email.PreviewProps || {};
32
+ const EmailComponent = Email as React.FC;
33
+ try {
34
+ const markup = await renderAsync(<EmailComponent {...previewProps} />, {
35
+ pretty: true,
36
+ });
37
+ const plainText = await renderAsync(<EmailComponent {...previewProps} />, {
38
+ plainText: true,
39
+ });
40
+
41
+ const reactMarkup = await fs.promises.readFile(emailPath, 'utf-8');
42
+
43
+ return {
44
+ markup,
45
+ plainText,
46
+ reactMarkup,
47
+ };
48
+ } catch (exception) {
49
+ const error = exception as Error;
50
+
51
+ return {
52
+ error: improveErrorWithSourceMap(
53
+ error,
54
+ emailPath,
55
+ sourceMapToOriginalFile,
56
+ ),
57
+ };
58
+ }
59
+ };
Binary file
@@ -0,0 +1,35 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ :root {
6
+ --foreground-rgb: 0, 0, 0;
7
+ --background-start-rgb: 214, 219, 220;
8
+ --background-end-rgb: 255, 255, 255;
9
+ }
10
+
11
+ @media (prefers-color-scheme: dark) {
12
+ :root {
13
+ --foreground-rgb: 255, 255, 255;
14
+ --background-start-rgb: 0, 0, 0;
15
+ --background-end-rgb: 0, 0, 0;
16
+ }
17
+ }
18
+
19
+ body {
20
+ color: rgb(var(--foreground-rgb));
21
+ background: linear-gradient(
22
+ to bottom,
23
+ transparent,
24
+ rgb(var(--background-end-rgb))
25
+ )
26
+ rgb(var(--background-start-rgb));
27
+ }
28
+
29
+ .popup-open iframe {
30
+ pointer-events: none;
31
+ }
32
+
33
+ nav > div > div > .line {
34
+ display: none;
35
+ }
@@ -0,0 +1,7 @@
1
+ import { Inter } from 'next/font/google';
2
+
3
+ export const inter = Inter({
4
+ subsets: ['latin'],
5
+ variable: '--font-inter',
6
+ display: 'swap',
7
+ });
@@ -0,0 +1,36 @@
1
+ import type { Metadata } from 'next';
2
+ import './globals.css';
3
+ import { getEmailsDirectoryMetadata } from '../actions/get-emails-directory-metadata';
4
+ import { emailsDirectoryAbsolutePath } from '../utils/emails-directory-absolute-path';
5
+ import { EmailsProvider } from '../contexts/emails';
6
+ import { inter } from './inter';
7
+
8
+ export const metadata: Metadata = {
9
+ title: 'React Email',
10
+ };
11
+
12
+ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
13
+ const emailsDirectoryMetadata = await getEmailsDirectoryMetadata(
14
+ emailsDirectoryAbsolutePath,
15
+ );
16
+
17
+ if (typeof emailsDirectoryMetadata === 'undefined') {
18
+ throw new Error(
19
+ `Could not find the emails directory specified under ${emailsDirectoryAbsolutePath}!`,
20
+ );
21
+ }
22
+
23
+ return (
24
+ <html lang="en">
25
+ <body className={inter.className}>
26
+ <EmailsProvider
27
+ initialEmailsDirectoryMetadata={emailsDirectoryMetadata}
28
+ >
29
+ {children}
30
+ </EmailsProvider>
31
+ </body>
32
+ </html>
33
+ );
34
+ };
35
+
36
+ export default RootLayout;
Binary file
@@ -0,0 +1,47 @@
1
+ import path from 'node:path';
2
+ import Link from 'next/link';
3
+ import Image from 'next/image';
4
+ import { Button, Heading, Text } from '../components';
5
+ import { Shell } from '../components/shell';
6
+ import { emailsDirectoryAbsolutePath } from '../utils/emails-directory-absolute-path';
7
+ import logo from './logo.png';
8
+
9
+ const Home = () => {
10
+ const baseEmailsDirectoryName = path.basename(emailsDirectoryAbsolutePath);
11
+
12
+ return (
13
+ <Shell>
14
+ <div className="relative max-w-lg mx-auto p-8 flex items-center justify-center h-[inherit]">
15
+ <div className="relative z-10 flex flex-col text-center items-center">
16
+ <Image
17
+ alt="React Email Icon"
18
+ className="mb-8"
19
+ height={144}
20
+ src={logo}
21
+ style={{
22
+ borderRadius: 34,
23
+ boxShadow: '0px 10px 200px 20px #2B7CA080',
24
+ }}
25
+ width={141}
26
+ />
27
+ <Heading as="h2" size="6" weight="medium">
28
+ Welcome to React Email
29
+ </Heading>
30
+ <Text as="p" className="mt-2 mb-4">
31
+ To start developing your emails, you can create a<br />
32
+ <code className="text-slate-12">.jsx</code> or{' '}
33
+ <code className="text-slate-12">.tsx</code> file under your{' '}
34
+ <code className="text-slate-12">{baseEmailsDirectoryName}</code>{' '}
35
+ folder.
36
+ </Text>
37
+
38
+ <Button asChild size="3">
39
+ <Link href="https://react.email/docs">Check the docs</Link>
40
+ </Button>
41
+ </div>
42
+ </div>
43
+ </Shell>
44
+ );
45
+ };
46
+
47
+ export default Home;
@@ -0,0 +1,65 @@
1
+ import { Suspense } from 'react';
2
+ import { getEmailPathFromSlug } from '../../../actions/get-email-path-from-slug';
3
+ import { getEmailsDirectoryMetadata } from '../../../actions/get-emails-directory-metadata';
4
+ import { renderEmailByPath } from '../../../actions/render-email-by-path';
5
+ import { emailsDirectoryAbsolutePath } from '../../../utils/emails-directory-absolute-path';
6
+ import Home from '../../page';
7
+ import Preview from './preview';
8
+
9
+ export const dynamicParams = true;
10
+
11
+ export interface PreviewParams {
12
+ slug: string[];
13
+ }
14
+
15
+ export default async function Page({ params }: { params: PreviewParams }) {
16
+ // will come in here as segments of a relative path to the email
17
+ // ex: ['authentication', 'verify-password.tsx']
18
+ const slug = params.slug.join('/');
19
+ const emailsDirMetadata = await getEmailsDirectoryMetadata(
20
+ emailsDirectoryAbsolutePath,
21
+ );
22
+
23
+ if (typeof emailsDirMetadata === 'undefined') {
24
+ throw new Error(
25
+ `Could not find the emails directory specified under ${emailsDirectoryAbsolutePath}!
26
+
27
+ This is most likely not an issue with the preview server. Maybe there was a typo on the "--dir" flag?`,
28
+ );
29
+ }
30
+
31
+ const emailPath = await getEmailPathFromSlug(slug);
32
+
33
+ const emailRenderingResult = await renderEmailByPath(emailPath);
34
+
35
+ if (
36
+ 'error' in emailRenderingResult &&
37
+ process.env.NEXT_PUBLIC_IS_BUILDING === 'true'
38
+ ) {
39
+ throw new Error(emailRenderingResult.error.message, {
40
+ cause: emailRenderingResult.error,
41
+ });
42
+ }
43
+
44
+ return (
45
+ // This suspense is so that this page doesn't throw warnings
46
+ // on the build of the preview server de-opting into
47
+ // client-side rendering on build
48
+ <Suspense fallback={<Home />}>
49
+ <Preview
50
+ emailPath={emailPath}
51
+ renderingResult={emailRenderingResult}
52
+ slug={slug}
53
+ />
54
+ </Suspense>
55
+ );
56
+ }
57
+
58
+ export function generateMetadata({ params }: { params: PreviewParams }) {
59
+ return { title: `${params.slug.join('/')} — React Email` };
60
+ }
61
+
62
+
63
+ export async function generateStaticParams() {
64
+ return [{"slug":["admin","abort-order-request"]},{"slug":["admin","revert-payment-request-admin"]},{"slug":["all","welcome"]},{"slug":["brand","evidences-accepted-brand"]},{"slug":["brand","evidences-submitted-brand"]},{"slug":["brand","new-job-application-received-brand"]},{"slug":["brand","new-job-created-brand"]},{"slug":["brand","new-order-created-brand"]},{"slug":["brand","order-accepted-brand"]},{"slug":["brand","order-cancelled-brand"]},{"slug":["brand","order-rejected-brand"]},{"slug":["creator","evidences-approved-creator"]},{"slug":["creator","evidences-rejected-creator"]},{"slug":["creator","new-job-application-unapproved-creator"]},{"slug":["creator","new-order-created-creator"]},{"slug":["creator","order-cancelled-creator"]},{"slug":["creator","order-payment-creator"]},{"slug":["shared","components","base-head"]},{"slug":["shared","components","comment-component"]},{"slug":["shared","components","footer"]},{"slug":["shared","components","header"]},{"slug":["shared","components","new-order-info"]},{"slug":["shared","components","payment-amount"]},{"slug":["shared","components","user-Info"]}];
65
+ }