react-email 1.3.0 → 1.4.0

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.
package/CHANGELOG.md CHANGED
@@ -1,12 +1,7 @@
1
1
  # react-email
2
2
 
3
- ## 1.3.0
3
+ ## 1.4.0
4
4
 
5
5
  ### Minor Changes
6
6
 
7
- - Add missing metadata such as `engines`, `repository` and images
8
-
9
- ### Patch Changes
10
-
11
- - Updated dependencies
12
- - @react-email/render@0.0.3
7
+ - Add static files support
@@ -12,11 +12,11 @@ exports.pages = [
12
12
  },
13
13
  {
14
14
  title: 'index.tsx',
15
- content: "import { promises as fs } from 'fs';\nimport path from 'path';\nimport { Button, Heading, Text } from '../components';\nimport { Layout } from '../components/layout';\nimport Link from 'next/link';\n\ninterface HomeProps {}\n\nexport const CONTENT_DIR = 'emails';\n\nconst getEmails = async () => {\n const emailsDirectory = path.join(process.cwd(), CONTENT_DIR);\n const filenames = await fs.readdir(emailsDirectory);\n const emails = filenames.map((file) => file.replace(/\\.(jsx|tsx)$/g, ''));\n\n return emails;\n};\n\nexport async function getStaticProps({ params }) {\n try {\n const emails = await getEmails();\n return emails\n ? { props: { navItems: emails } }\n : { props: { navItems: [] } };\n } catch (error) {\n console.error(error);\n return { props: { navItems: [] } };\n }\n}\n\nconst Home: React.FC<Readonly<HomeProps>> = ({ navItems }: any) => {\n return (\n <Layout navItems={navItems}>\n <div className=\"max-w-md border border-slate-6 mx-auto mt-56 rounded-md p-8\">\n <Heading as=\"h2\" weight=\"medium\">\n Welcome to the React Email preview!\n </Heading>\n <Text as=\"p\" className=\"mt-2 mb-4\">\n To start developing your next email template, you can create a{' '}\n <code>.jsx</code> or <code>.tsx</code> file under the \"emails\" folder.\n </Text>\n\n <Button asChild>\n <Link href=\"https://react.email/docs\">Check the docs</Link>\n </Button>\n </div>\n </Layout>\n );\n};\n\nexport default Home;\n",
15
+ content: "import { promises as fs } from 'fs';\nimport path from 'path';\nimport { Button, Heading, Text } from '../components';\nimport { Layout } from '../components/layout';\nimport Link from 'next/link';\n\ninterface HomeProps {}\n\nexport const CONTENT_DIR = 'emails';\n\nconst getEmails = async () => {\n const emailsDirectory = path.join(process.cwd(), CONTENT_DIR);\n\n const filenames = await fs.readdir(emailsDirectory);\n const emails = filenames\n .map((file) => file.replace(/\\.(jsx|tsx)$/g, ''))\n .filter((file) => file !== 'components');\n\n return emails;\n};\n\nexport async function getStaticProps({ params }) {\n try {\n const emails = await getEmails();\n return emails\n ? { props: { navItems: emails } }\n : { props: { navItems: [] } };\n } catch (error) {\n console.error(error);\n return { props: { navItems: [] } };\n }\n}\n\nconst Home: React.FC<Readonly<HomeProps>> = ({ navItems }: any) => {\n return (\n <Layout navItems={navItems}>\n <div className=\"max-w-md border border-slate-6 mx-auto mt-56 rounded-md p-8\">\n <Heading as=\"h2\" weight=\"medium\">\n Welcome to the React Email preview!\n </Heading>\n <Text as=\"p\" className=\"mt-2 mb-4\">\n To start developing your next email template, you can create a{' '}\n <code>.jsx</code> or <code>.tsx</code> file under the \"emails\" folder.\n </Text>\n\n <Button asChild>\n <Link href=\"https://react.email/docs\">Check the docs</Link>\n </Button>\n </div>\n </Layout>\n );\n};\n\nexport default Home;\n",
16
16
  },
17
17
  {
18
18
  dir: 'preview',
19
19
  title: '[slug].tsx',
20
- content: "import { promises as fs } from 'fs';\nimport path from 'path';\nimport { render } from '@react-email/render';\nimport { GetStaticPaths } from 'next';\nimport { Layout } from '../../components/layout';\nimport * as React from 'react';\nimport { Code } from '../../components';\nimport Head from 'next/head';\n\ninterface PreviewProps {}\n\nexport const CONTENT_DIR = 'emails';\n\nconst getEmails = async () => {\n const emailsDirectory = path.join(process.cwd(), CONTENT_DIR);\n const filenames = await fs.readdir(emailsDirectory);\n const emails = filenames.map((file) => file.replace(/\\.(jsx|tsx)$/g, ''));\n return { emails, filenames };\n};\n\nexport const getStaticPaths: GetStaticPaths = async () => {\n const { emails } = await getEmails();\n const paths = emails.map((email) => {\n return { params: { slug: email } };\n });\n return { paths, fallback: true };\n};\n\nexport async function getStaticProps({ params }) {\n try {\n const { emails, filenames } = await getEmails();\n const template = filenames.filter((email) => {\n const [fileName] = email.split('.');\n return params.slug === fileName;\n });\n\n const Email = (await import(`../../../emails/${params.slug}`)).default;\n const markup = render(<Email />, { pretty: true });\n const path = `${process.cwd()}/${CONTENT_DIR}/${template[0]}`;\n const reactMarkup = await fs.readFile(path, {\n encoding: 'utf-8',\n });\n\n return emails\n ? { props: { navItems: emails, slug: params.slug, markup, reactMarkup } }\n : { notFound: true };\n } catch (error) {\n console.error(error);\n return { notFound: true };\n }\n}\n\nconst Preview: React.FC<Readonly<PreviewProps>> = ({\n navItems,\n markup,\n reactMarkup,\n slug,\n}: any) => {\n const [viewMode, setViewMode] = React.useState('desktop');\n const title = `${slug} React Email`;\n\n return (\n <Layout\n navItems={navItems}\n title={slug}\n viewMode={viewMode}\n setViewMode={setViewMode}\n >\n <Head>\n <title>{title}</title>\n </Head>\n {viewMode === 'desktop' ? (\n <iframe\n srcDoc={markup}\n frameBorder=\"0\"\n className=\"w-full h-[calc(100vh_-_70px)]\"\n />\n ) : (\n <div className=\"flex gap-6 mx-auto p-6\">\n <Code>{reactMarkup}</Code>\n <Code>{markup}</Code>\n </div>\n )}\n </Layout>\n );\n};\n\nexport default Preview;\n",
20
+ content: "import * as React from 'react';\nimport { promises as fs } from 'fs';\nimport path from 'path';\nimport { render } from '@react-email/render';\nimport { GetStaticPaths } from 'next';\nimport { Layout } from '../../components/layout';\nimport { Code } from '../../components';\nimport Head from 'next/head';\nimport { useRouter } from 'next/router';\n\ninterface PreviewProps {}\n\nexport const CONTENT_DIR = 'emails';\n\nconst getEmails = async () => {\n const emailsDirectory = path.join(process.cwd(), CONTENT_DIR);\n const filenames = await fs.readdir(emailsDirectory);\n const emails = filenames\n .map((file) => file.replace(/\\.(jsx|tsx)$/g, ''))\n .filter((file) => file !== 'components');\n return { emails, filenames };\n};\n\nexport const getStaticPaths: GetStaticPaths = async () => {\n const { emails } = await getEmails();\n const paths = emails.map((email) => {\n return { params: { slug: email } };\n });\n return { paths, fallback: true };\n};\n\nexport async function getStaticProps({ params }) {\n try {\n const { emails, filenames } = await getEmails();\n const template = filenames.filter((email) => {\n const [fileName] = email.split('.');\n return params.slug === fileName;\n });\n\n const Email = (await import(`../../../emails/${params.slug}`)).default;\n const markup = render(<Email />, { pretty: true });\n const path = `${process.cwd()}/${CONTENT_DIR}/${template[0]}`;\n const reactMarkup = await fs.readFile(path, {\n encoding: 'utf-8',\n });\n\n return emails\n ? { props: { navItems: emails, slug: params.slug, markup, reactMarkup } }\n : { notFound: true };\n } catch (error) {\n console.error(error);\n return { notFound: true };\n }\n}\n\nconst Preview: React.FC<Readonly<PreviewProps>> = ({\n navItems,\n markup,\n reactMarkup,\n slug,\n}: any) => {\n const title = `${slug} — React Email`;\n const router = useRouter();\n const [viewMode, setViewMode] = React.useState('desktop');\n\n const handleViewMode = (mode: string) => {\n setViewMode(mode);\n\n router.push({\n pathname: router.pathname,\n query: {\n ...router.query,\n view: mode,\n },\n });\n };\n\n React.useEffect(() => {\n if (router.query.view === 'source' || router.query.view === 'desktop') {\n setViewMode(router.query.view);\n }\n }, [router.query.view]);\n\n return (\n <Layout\n navItems={navItems}\n title={slug}\n viewMode={viewMode}\n setViewMode={handleViewMode}\n >\n <Head>\n <title>{title}</title>\n </Head>\n {viewMode === 'desktop' ? (\n <iframe\n srcDoc={markup}\n frameBorder=\"0\"\n className=\"w-full h-[calc(100vh_-_70px)]\"\n />\n ) : (\n <div className=\"flex gap-6 mx-auto p-6\">\n <Code>{reactMarkup}</Code>\n <Code>{markup}</Code>\n </div>\n )}\n </Layout>\n );\n};\n\nexport default Preview;\n",
21
21
  },
22
22
  ];
@@ -8,7 +8,7 @@ exports.root = [
8
8
  },
9
9
  {
10
10
  title: 'package.json',
11
- content: '{\n "name": "react-email-preview",\n "version": "0.0.4",\n "description": "The React Email preview application",\n "license": "MIT",\n "scripts": {\n "dev": "next dev",\n "build": "next build",\n "start": "next start",\n "lint": "next lint",\n "format:check": "prettier --check \\"**/*.{ts,tsx,md}\\"",\n "format": "prettier --write \\"**/*.{ts,tsx,md}\\""\n },\n "dependencies": {\n "@next/font": "13.0.4",\n "@radix-ui/colors": "0.1.8",\n "@radix-ui/react-collapsible": "1.0.1",\n "@radix-ui/react-slot": "1.0.1",\n "@radix-ui/react-toggle-group": "1.0.1",\n "@react-email/render": "0.0.2",\n "classnames": "2.3.2",\n "next": "13.0.4",\n "prism-react-renderer": "1.3.5",\n "react": "18.2.0",\n "react-dom": "18.2.0"\n },\n "devDependencies": {\n "@types/classnames": "2.3.1",\n "@types/node": "18.11.9",\n "@types/react": "18.0.25",\n "@types/react-dom": "18.0.9",\n "autoprefixer": "10.4.13",\n "postcss": "8.4.19",\n "tailwindcss": "3.2.4",\n "typescript": "4.9.3"\n }\n}\n',
11
+ content: '{\n "name": "react-email-preview",\n "version": "0.0.4",\n "description": "The React Email preview application",\n "license": "MIT",\n "scripts": {\n "dev": "next dev",\n "build": "next build",\n "start": "next start",\n "lint": "next lint",\n "format:check": "prettier --check \\"**/*.{ts,tsx,md}\\"",\n "format": "prettier --write \\"**/*.{ts,tsx,md}\\""\n },\n "engines": {\n "node": ">=18.0.0"\n },\n "dependencies": {\n "@next/font": "13.0.4",\n "@radix-ui/colors": "0.1.8",\n "@radix-ui/react-collapsible": "1.0.1",\n "@radix-ui/react-slot": "1.0.1",\n "@radix-ui/react-toggle-group": "1.0.1",\n "@react-email/render": "0.0.2",\n "classnames": "2.3.2",\n "next": "13.0.4",\n "prism-react-renderer": "1.3.5",\n "react": "18.2.0",\n "react-dom": "18.2.0"\n },\n "devDependencies": {\n "@types/classnames": "2.3.1",\n "@types/node": "18.11.9",\n "@types/react": "18.0.25",\n "@types/react-dom": "18.0.9",\n "autoprefixer": "10.4.13",\n "postcss": "8.4.19",\n "tailwindcss": "3.2.4",\n "typescript": "4.9.3"\n }\n}',
12
12
  },
13
13
  {
14
14
  title: 'postcss.config.js',
@@ -31,11 +31,12 @@ const dev = async () => {
31
31
  };
32
32
  exports.dev = dev;
33
33
  const prepareFiles = async () => {
34
- const spinner = (0, ora_1.default)('Preparing React email files...').start();
34
+ const spinner = (0, ora_1.default)('Preparing React Email files...').start();
35
35
  const isFirstTime = !(0, check_directory_exist_1.checkDirectoryExist)(constants_1.REACT_EMAIL_ROOT);
36
36
  if (isFirstTime) {
37
37
  await (0, create_directory_1.createDirectory)(constants_1.REACT_EMAIL_ROOT);
38
38
  await (0, create_directory_1.createDirectory)(path_1.default.join(constants_1.REACT_EMAIL_ROOT, 'src'));
39
+ await (0, create_directory_1.createDirectory)(path_1.default.join(constants_1.REACT_EMAIL_ROOT, 'public'));
39
40
  await Promise.all([
40
41
  createFilesAndDirectories(components_1.components, 'components'),
41
42
  createFilesAndDirectories(utils_1.utils, 'utils'),
@@ -56,15 +57,14 @@ const checkForUpdates = async () => {
56
57
  if (isUpToDate) {
57
58
  return spinner.stopAndPersist({
58
59
  symbol: log_symbols_1.default.success,
59
- text: 'React email is up-to-date',
60
+ text: 'React Email is up-to-date',
60
61
  });
61
62
  }
62
63
  return updatePackage();
63
64
  };
64
65
  const updatePackage = async () => {
65
- const spinner = (0, ora_1.default)('Updating React email...').start();
66
+ const spinner = (0, ora_1.default)('Updating React Email...').start();
66
67
  await Promise.all([
67
- createFilesAndDirectories(components_1.components, 'components'),
68
68
  createFilesAndDirectories(utils_1.utils, 'utils'),
69
69
  createFilesAndDirectories(styles_1.styles, 'styles'),
70
70
  createFilesAndDirectories(root_1.root),
@@ -72,18 +72,25 @@ const updatePackage = async () => {
72
72
  ]);
73
73
  spinner.stopAndPersist({
74
74
  symbol: log_symbols_1.default.success,
75
- text: 'React email is updated',
75
+ text: 'React Email is updated',
76
76
  });
77
77
  };
78
78
  const generateEmailsPreview = async () => {
79
79
  const spinner = (0, ora_1.default)('Generating emails preview').start();
80
80
  const hasEmailsDirectory = fs_1.default.existsSync(constants_1.CLIENT_EMAILS_PATH);
81
81
  const hasPackageEmailsDirectory = fs_1.default.existsSync(constants_1.PACKAGE_EMAILS_PATH);
82
+ const hasPackagePublicDirectory = fs_1.default.existsSync(`${constants_1.REACT_EMAIL_ROOT}/public/static`);
82
83
  if (hasEmailsDirectory) {
83
84
  if (hasPackageEmailsDirectory) {
84
- await fs_1.default.promises.rmdir(constants_1.PACKAGE_EMAILS_PATH, { recursive: true });
85
+ await fs_1.default.promises.rm(constants_1.PACKAGE_EMAILS_PATH, { recursive: true });
85
86
  }
86
- await (0, cpy_1.default)(constants_1.CLIENT_EMAILS_PATH, constants_1.PACKAGE_EMAILS_PATH);
87
+ if (hasPackagePublicDirectory) {
88
+ await fs_1.default.promises.rm(`${constants_1.REACT_EMAIL_ROOT}/public`, {
89
+ recursive: true,
90
+ });
91
+ }
92
+ await (0, cpy_1.default)(`${constants_1.CLIENT_EMAILS_PATH}/*{.tsx,.jsx}`, constants_1.PACKAGE_EMAILS_PATH);
93
+ await (0, cpy_1.default)(`${constants_1.CLIENT_EMAILS_PATH}/static`, `${constants_1.REACT_EMAIL_ROOT}/public/static`);
87
94
  return spinner.stopAndPersist({
88
95
  symbol: log_symbols_1.default.success,
89
96
  text: 'Emails preview generated',
Binary file
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+
3
+ export default () => <h1>AA</h1>;
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+
3
+ export default () => <h1>AA</h1>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-email",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "A live preview of your emails right in your browser.",
5
5
  "bin": {
6
6
  "email": "./dist/index.js"
@@ -47,4 +47,4 @@
47
47
  "prettier": "2.7.1",
48
48
  "ts-node": "10.9.1"
49
49
  }
50
- }
50
+ }
@@ -37,4 +37,4 @@
37
37
  "tailwindcss": "3.2.4",
38
38
  "typescript": "4.9.3"
39
39
  }
40
- }
40
+ }
@@ -10,8 +10,11 @@ export const CONTENT_DIR = 'emails';
10
10
 
11
11
  const getEmails = async () => {
12
12
  const emailsDirectory = path.join(process.cwd(), CONTENT_DIR);
13
+
13
14
  const filenames = await fs.readdir(emailsDirectory);
14
- const emails = filenames.map((file) => file.replace(/\.(jsx|tsx)$/g, ''));
15
+ const emails = filenames
16
+ .map((file) => file.replace(/\.(jsx|tsx)$/g, ''))
17
+ .filter((file) => file !== 'components');
15
18
 
16
19
  return emails;
17
20
  };
@@ -15,7 +15,9 @@ export const CONTENT_DIR = 'emails';
15
15
  const getEmails = async () => {
16
16
  const emailsDirectory = path.join(process.cwd(), CONTENT_DIR);
17
17
  const filenames = await fs.readdir(emailsDirectory);
18
- const emails = filenames.map((file) => file.replace(/\.(jsx|tsx)$/g, ''));
18
+ const emails = filenames
19
+ .map((file) => file.replace(/\.(jsx|tsx)$/g, ''))
20
+ .filter((file) => file !== 'components');
19
21
  return { emails, filenames };
20
22
  };
21
23
 
@@ -12,12 +12,12 @@ export const pages = [
12
12
  {
13
13
  title: 'index.tsx',
14
14
  content:
15
- "import { promises as fs } from 'fs';\nimport path from 'path';\nimport { Button, Heading, Text } from '../components';\nimport { Layout } from '../components/layout';\nimport Link from 'next/link';\n\ninterface HomeProps {}\n\nexport const CONTENT_DIR = 'emails';\n\nconst getEmails = async () => {\n const emailsDirectory = path.join(process.cwd(), CONTENT_DIR);\n const filenames = await fs.readdir(emailsDirectory);\n const emails = filenames.map((file) => file.replace(/\\.(jsx|tsx)$/g, ''));\n\n return emails;\n};\n\nexport async function getStaticProps({ params }) {\n try {\n const emails = await getEmails();\n return emails\n ? { props: { navItems: emails } }\n : { props: { navItems: [] } };\n } catch (error) {\n console.error(error);\n return { props: { navItems: [] } };\n }\n}\n\nconst Home: React.FC<Readonly<HomeProps>> = ({ navItems }: any) => {\n return (\n <Layout navItems={navItems}>\n <div className=\"max-w-md border border-slate-6 mx-auto mt-56 rounded-md p-8\">\n <Heading as=\"h2\" weight=\"medium\">\n Welcome to the React Email preview!\n </Heading>\n <Text as=\"p\" className=\"mt-2 mb-4\">\n To start developing your next email template, you can create a{' '}\n <code>.jsx</code> or <code>.tsx</code> file under the \"emails\" folder.\n </Text>\n\n <Button asChild>\n <Link href=\"https://react.email/docs\">Check the docs</Link>\n </Button>\n </div>\n </Layout>\n );\n};\n\nexport default Home;\n",
15
+ "import { promises as fs } from 'fs';\nimport path from 'path';\nimport { Button, Heading, Text } from '../components';\nimport { Layout } from '../components/layout';\nimport Link from 'next/link';\n\ninterface HomeProps {}\n\nexport const CONTENT_DIR = 'emails';\n\nconst getEmails = async () => {\n const emailsDirectory = path.join(process.cwd(), CONTENT_DIR);\n\n const filenames = await fs.readdir(emailsDirectory);\n const emails = filenames\n .map((file) => file.replace(/\\.(jsx|tsx)$/g, ''))\n .filter((file) => file !== 'components');\n\n return emails;\n};\n\nexport async function getStaticProps({ params }) {\n try {\n const emails = await getEmails();\n return emails\n ? { props: { navItems: emails } }\n : { props: { navItems: [] } };\n } catch (error) {\n console.error(error);\n return { props: { navItems: [] } };\n }\n}\n\nconst Home: React.FC<Readonly<HomeProps>> = ({ navItems }: any) => {\n return (\n <Layout navItems={navItems}>\n <div className=\"max-w-md border border-slate-6 mx-auto mt-56 rounded-md p-8\">\n <Heading as=\"h2\" weight=\"medium\">\n Welcome to the React Email preview!\n </Heading>\n <Text as=\"p\" className=\"mt-2 mb-4\">\n To start developing your next email template, you can create a{' '}\n <code>.jsx</code> or <code>.tsx</code> file under the \"emails\" folder.\n </Text>\n\n <Button asChild>\n <Link href=\"https://react.email/docs\">Check the docs</Link>\n </Button>\n </div>\n </Layout>\n );\n};\n\nexport default Home;\n",
16
16
  },
17
17
  {
18
18
  dir: 'preview',
19
19
  title: '[slug].tsx',
20
20
  content:
21
- "import { promises as fs } from 'fs';\nimport path from 'path';\nimport { render } from '@react-email/render';\nimport { GetStaticPaths } from 'next';\nimport { Layout } from '../../components/layout';\nimport * as React from 'react';\nimport { Code } from '../../components';\nimport Head from 'next/head';\n\ninterface PreviewProps {}\n\nexport const CONTENT_DIR = 'emails';\n\nconst getEmails = async () => {\n const emailsDirectory = path.join(process.cwd(), CONTENT_DIR);\n const filenames = await fs.readdir(emailsDirectory);\n const emails = filenames.map((file) => file.replace(/\\.(jsx|tsx)$/g, ''));\n return { emails, filenames };\n};\n\nexport const getStaticPaths: GetStaticPaths = async () => {\n const { emails } = await getEmails();\n const paths = emails.map((email) => {\n return { params: { slug: email } };\n });\n return { paths, fallback: true };\n};\n\nexport async function getStaticProps({ params }) {\n try {\n const { emails, filenames } = await getEmails();\n const template = filenames.filter((email) => {\n const [fileName] = email.split('.');\n return params.slug === fileName;\n });\n\n const Email = (await import(`../../../emails/${params.slug}`)).default;\n const markup = render(<Email />, { pretty: true });\n const path = `${process.cwd()}/${CONTENT_DIR}/${template[0]}`;\n const reactMarkup = await fs.readFile(path, {\n encoding: 'utf-8',\n });\n\n return emails\n ? { props: { navItems: emails, slug: params.slug, markup, reactMarkup } }\n : { notFound: true };\n } catch (error) {\n console.error(error);\n return { notFound: true };\n }\n}\n\nconst Preview: React.FC<Readonly<PreviewProps>> = ({\n navItems,\n markup,\n reactMarkup,\n slug,\n}: any) => {\n const [viewMode, setViewMode] = React.useState('desktop');\n const title = `${slug} React Email`;\n\n return (\n <Layout\n navItems={navItems}\n title={slug}\n viewMode={viewMode}\n setViewMode={setViewMode}\n >\n <Head>\n <title>{title}</title>\n </Head>\n {viewMode === 'desktop' ? (\n <iframe\n srcDoc={markup}\n frameBorder=\"0\"\n className=\"w-full h-[calc(100vh_-_70px)]\"\n />\n ) : (\n <div className=\"flex gap-6 mx-auto p-6\">\n <Code>{reactMarkup}</Code>\n <Code>{markup}</Code>\n </div>\n )}\n </Layout>\n );\n};\n\nexport default Preview;\n",
21
+ "import * as React from 'react';\nimport { promises as fs } from 'fs';\nimport path from 'path';\nimport { render } from '@react-email/render';\nimport { GetStaticPaths } from 'next';\nimport { Layout } from '../../components/layout';\nimport { Code } from '../../components';\nimport Head from 'next/head';\nimport { useRouter } from 'next/router';\n\ninterface PreviewProps {}\n\nexport const CONTENT_DIR = 'emails';\n\nconst getEmails = async () => {\n const emailsDirectory = path.join(process.cwd(), CONTENT_DIR);\n const filenames = await fs.readdir(emailsDirectory);\n const emails = filenames\n .map((file) => file.replace(/\\.(jsx|tsx)$/g, ''))\n .filter((file) => file !== 'components');\n return { emails, filenames };\n};\n\nexport const getStaticPaths: GetStaticPaths = async () => {\n const { emails } = await getEmails();\n const paths = emails.map((email) => {\n return { params: { slug: email } };\n });\n return { paths, fallback: true };\n};\n\nexport async function getStaticProps({ params }) {\n try {\n const { emails, filenames } = await getEmails();\n const template = filenames.filter((email) => {\n const [fileName] = email.split('.');\n return params.slug === fileName;\n });\n\n const Email = (await import(`../../../emails/${params.slug}`)).default;\n const markup = render(<Email />, { pretty: true });\n const path = `${process.cwd()}/${CONTENT_DIR}/${template[0]}`;\n const reactMarkup = await fs.readFile(path, {\n encoding: 'utf-8',\n });\n\n return emails\n ? { props: { navItems: emails, slug: params.slug, markup, reactMarkup } }\n : { notFound: true };\n } catch (error) {\n console.error(error);\n return { notFound: true };\n }\n}\n\nconst Preview: React.FC<Readonly<PreviewProps>> = ({\n navItems,\n markup,\n reactMarkup,\n slug,\n}: any) => {\n const title = `${slug} — React Email`;\n const router = useRouter();\n const [viewMode, setViewMode] = React.useState('desktop');\n\n const handleViewMode = (mode: string) => {\n setViewMode(mode);\n\n router.push({\n pathname: router.pathname,\n query: {\n ...router.query,\n view: mode,\n },\n });\n };\n\n React.useEffect(() => {\n if (router.query.view === 'source' || router.query.view === 'desktop') {\n setViewMode(router.query.view);\n }\n }, [router.query.view]);\n\n return (\n <Layout\n navItems={navItems}\n title={slug}\n viewMode={viewMode}\n setViewMode={handleViewMode}\n >\n <Head>\n <title>{title}</title>\n </Head>\n {viewMode === 'desktop' ? (\n <iframe\n srcDoc={markup}\n frameBorder=\"0\"\n className=\"w-full h-[calc(100vh_-_70px)]\"\n />\n ) : (\n <div className=\"flex gap-6 mx-auto p-6\">\n <Code>{reactMarkup}</Code>\n <Code>{markup}</Code>\n </div>\n )}\n </Layout>\n );\n};\n\nexport default Preview;\n",
22
22
  },
23
23
  ];
@@ -7,7 +7,7 @@ export const root = [
7
7
  {
8
8
  title: 'package.json',
9
9
  content:
10
- '{\n "name": "react-email-preview",\n "version": "0.0.4",\n "description": "The React Email preview application",\n "license": "MIT",\n "scripts": {\n "dev": "next dev",\n "build": "next build",\n "start": "next start",\n "lint": "next lint",\n "format:check": "prettier --check \\"**/*.{ts,tsx,md}\\"",\n "format": "prettier --write \\"**/*.{ts,tsx,md}\\""\n },\n "dependencies": {\n "@next/font": "13.0.4",\n "@radix-ui/colors": "0.1.8",\n "@radix-ui/react-collapsible": "1.0.1",\n "@radix-ui/react-slot": "1.0.1",\n "@radix-ui/react-toggle-group": "1.0.1",\n "@react-email/render": "0.0.2",\n "classnames": "2.3.2",\n "next": "13.0.4",\n "prism-react-renderer": "1.3.5",\n "react": "18.2.0",\n "react-dom": "18.2.0"\n },\n "devDependencies": {\n "@types/classnames": "2.3.1",\n "@types/node": "18.11.9",\n "@types/react": "18.0.25",\n "@types/react-dom": "18.0.9",\n "autoprefixer": "10.4.13",\n "postcss": "8.4.19",\n "tailwindcss": "3.2.4",\n "typescript": "4.9.3"\n }\n}\n',
10
+ '{\n "name": "react-email-preview",\n "version": "0.0.4",\n "description": "The React Email preview application",\n "license": "MIT",\n "scripts": {\n "dev": "next dev",\n "build": "next build",\n "start": "next start",\n "lint": "next lint",\n "format:check": "prettier --check \\"**/*.{ts,tsx,md}\\"",\n "format": "prettier --write \\"**/*.{ts,tsx,md}\\""\n },\n "engines": {\n "node": ">=18.0.0"\n },\n "dependencies": {\n "@next/font": "13.0.4",\n "@radix-ui/colors": "0.1.8",\n "@radix-ui/react-collapsible": "1.0.1",\n "@radix-ui/react-slot": "1.0.1",\n "@radix-ui/react-toggle-group": "1.0.1",\n "@react-email/render": "0.0.2",\n "classnames": "2.3.2",\n "next": "13.0.4",\n "prism-react-renderer": "1.3.5",\n "react": "18.2.0",\n "react-dom": "18.2.0"\n },\n "devDependencies": {\n "@types/classnames": "2.3.1",\n "@types/node": "18.11.9",\n "@types/react": "18.0.25",\n "@types/react-dom": "18.0.9",\n "autoprefixer": "10.4.13",\n "postcss": "8.4.19",\n "tailwindcss": "3.2.4",\n "typescript": "4.9.3"\n }\n}',
11
11
  },
12
12
  {
13
13
  title: 'postcss.config.js',
@@ -31,12 +31,13 @@ export const dev = async () => {
31
31
  };
32
32
 
33
33
  const prepareFiles = async () => {
34
- const spinner = ora('Preparing React email files...').start();
34
+ const spinner = ora('Preparing React Email files...').start();
35
35
  const isFirstTime = !checkDirectoryExist(REACT_EMAIL_ROOT);
36
36
 
37
37
  if (isFirstTime) {
38
38
  await createDirectory(REACT_EMAIL_ROOT);
39
39
  await createDirectory(path.join(REACT_EMAIL_ROOT, 'src'));
40
+ await createDirectory(path.join(REACT_EMAIL_ROOT, 'public'));
40
41
 
41
42
  await Promise.all([
42
43
  createFilesAndDirectories(components, 'components'),
@@ -65,7 +66,7 @@ const checkForUpdates = async () => {
65
66
  if (isUpToDate) {
66
67
  return spinner.stopAndPersist({
67
68
  symbol: logSymbols.success,
68
- text: 'React email is up-to-date',
69
+ text: 'React Email is up-to-date',
69
70
  });
70
71
  }
71
72
 
@@ -73,10 +74,9 @@ const checkForUpdates = async () => {
73
74
  };
74
75
 
75
76
  const updatePackage = async () => {
76
- const spinner = ora('Updating React email...').start();
77
+ const spinner = ora('Updating React Email...').start();
77
78
 
78
79
  await Promise.all([
79
- createFilesAndDirectories(components, 'components'),
80
80
  createFilesAndDirectories(utils, 'utils'),
81
81
  createFilesAndDirectories(styles, 'styles'),
82
82
  createFilesAndDirectories(root),
@@ -85,7 +85,7 @@ const updatePackage = async () => {
85
85
 
86
86
  spinner.stopAndPersist({
87
87
  symbol: logSymbols.success,
88
- text: 'React email is updated',
88
+ text: 'React Email is updated',
89
89
  });
90
90
  };
91
91
 
@@ -93,13 +93,26 @@ const generateEmailsPreview = async () => {
93
93
  const spinner = ora('Generating emails preview').start();
94
94
  const hasEmailsDirectory = fs.existsSync(CLIENT_EMAILS_PATH);
95
95
  const hasPackageEmailsDirectory = fs.existsSync(PACKAGE_EMAILS_PATH);
96
+ const hasPackagePublicDirectory = fs.existsSync(
97
+ `${REACT_EMAIL_ROOT}/public/static`,
98
+ );
96
99
 
97
100
  if (hasEmailsDirectory) {
98
101
  if (hasPackageEmailsDirectory) {
99
- await fs.promises.rmdir(PACKAGE_EMAILS_PATH, { recursive: true });
102
+ await fs.promises.rm(PACKAGE_EMAILS_PATH, { recursive: true });
103
+ }
104
+
105
+ if (hasPackagePublicDirectory) {
106
+ await fs.promises.rm(`${REACT_EMAIL_ROOT}/public`, {
107
+ recursive: true,
108
+ });
100
109
  }
101
110
 
102
- await copy(CLIENT_EMAILS_PATH, PACKAGE_EMAILS_PATH);
111
+ await copy(`${CLIENT_EMAILS_PATH}/*{.tsx,.jsx}`, PACKAGE_EMAILS_PATH);
112
+ await copy(
113
+ `${CLIENT_EMAILS_PATH}/static`,
114
+ `${REACT_EMAIL_ROOT}/public/static`,
115
+ );
103
116
  return spinner.stopAndPersist({
104
117
  symbol: logSymbols.success,
105
118
  text: 'Emails preview generated',