feat: monorepo
This commit is contained in:
25
packages/email/.react-email/src/app/home.tsx
Normal file
25
packages/email/.react-email/src/app/home.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { Button, Heading, Text } from '../components';
|
||||
import { Shell } from '../components/shell';
|
||||
|
||||
export default function Home({ navItems }) {
|
||||
return (
|
||||
<Shell navItems={navItems}>
|
||||
<div className="max-w-md border border-slate-6 mx-auto mt-56 rounded-md p-8">
|
||||
<Heading as="h2" weight="medium">
|
||||
Welcome to the React Email preview!
|
||||
</Heading>
|
||||
<Text as="p" className="mt-2 mb-4">
|
||||
To start developing your next email template, you can create a{' '}
|
||||
<code>.jsx</code> or <code>.tsx</code> file under the "emails" folder.
|
||||
</Text>
|
||||
|
||||
<Button asChild>
|
||||
<Link href="https://react.email/docs">Check the docs</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
24
packages/email/.react-email/src/app/layout.tsx
Normal file
24
packages/email/.react-email/src/app/layout.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import '../styles/globals.css';
|
||||
import classnames from 'classnames';
|
||||
import { Inter } from 'next/font/google';
|
||||
|
||||
export const inter = Inter({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-inter',
|
||||
});
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className="bg-black text-slate-12 font-sans">
|
||||
<div className={classnames(inter.variable, 'font-sans')}>
|
||||
{children}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
11
packages/email/.react-email/src/app/page.tsx
Normal file
11
packages/email/.react-email/src/app/page.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { getEmails } from '../utils/get-emails';
|
||||
import Home from './home';
|
||||
|
||||
export default async function Index() {
|
||||
const { emails } = await getEmails();
|
||||
return <Home navItems={emails} />;
|
||||
}
|
||||
|
||||
export const metadata = {
|
||||
title: 'React Email',
|
||||
};
|
||||
56
packages/email/.react-email/src/app/preview/[slug]/page.tsx
Normal file
56
packages/email/.react-email/src/app/preview/[slug]/page.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { render } from '@react-email/render';
|
||||
import { promises as fs } from 'fs';
|
||||
import { dirname, join as pathJoin } from 'path';
|
||||
import { CONTENT_DIR, getEmails } from '../../../utils/get-emails';
|
||||
import Preview from './preview';
|
||||
|
||||
export const dynamicParams = true;
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const { emails } = await getEmails();
|
||||
|
||||
const paths = emails.map((email) => {
|
||||
return { slug: email };
|
||||
});
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
export default async function Page({ params }) {
|
||||
const { emails, filenames } = await getEmails();
|
||||
const template = filenames.filter((email) => {
|
||||
const [fileName] = email.split('.');
|
||||
return params.slug === fileName;
|
||||
});
|
||||
|
||||
const Email = (await import(`../../../../emails/${params.slug}`)).default;
|
||||
const markup = render(<Email />, { pretty: true });
|
||||
const plainText = render(<Email />, { plainText: true });
|
||||
const basePath = pathJoin(process.cwd(), CONTENT_DIR);
|
||||
const path = pathJoin(basePath, template[0]);
|
||||
|
||||
// the file is actually just re-exporting the default export of the original file. We need to resolve this first
|
||||
const exportTemplateFile: string = await fs.readFile(path, {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
const importPath = exportTemplateFile.match(/import Mail from '(.+)';/)![1];
|
||||
const originalFilePath = pathJoin(dirname(path), importPath);
|
||||
|
||||
const reactMarkup: string = await fs.readFile(originalFilePath, {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
return (
|
||||
<Preview
|
||||
navItems={emails}
|
||||
slug={params.slug}
|
||||
markup={markup}
|
||||
reactMarkup={reactMarkup}
|
||||
plainText={plainText}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }) {
|
||||
return { title: `${params.slug} — React Email` };
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
'use client';
|
||||
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
import React from 'react';
|
||||
import { CodeContainer } from '../../../components/code-container';
|
||||
import { Shell } from '../../../components/shell';
|
||||
import { Tooltip } from '../../../components/tooltip';
|
||||
|
||||
export default function Preview({
|
||||
navItems,
|
||||
slug,
|
||||
markup,
|
||||
reactMarkup,
|
||||
plainText,
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const [activeView, setActiveView] = React.useState('desktop');
|
||||
const [activeLang, setActiveLang] = React.useState('jsx');
|
||||
|
||||
React.useEffect(() => {
|
||||
const view = searchParams.get('view');
|
||||
const lang = searchParams.get('lang');
|
||||
|
||||
if (view === 'source' || view === 'desktop') {
|
||||
setActiveView(view);
|
||||
}
|
||||
|
||||
if (lang === 'jsx' || lang === 'markup' || lang === 'markdown') {
|
||||
setActiveLang(lang);
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
const handleViewChange = (view: string) => {
|
||||
setActiveView(view);
|
||||
router.push(`${pathname}?view=${view}`);
|
||||
};
|
||||
|
||||
const handleLangChange = (lang: string) => {
|
||||
setActiveLang(lang);
|
||||
router.push(`${pathname}?view=source&lang=${lang}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Shell
|
||||
navItems={navItems}
|
||||
title={slug}
|
||||
markup={markup}
|
||||
activeView={activeView}
|
||||
setActiveView={handleViewChange}
|
||||
>
|
||||
{activeView === 'desktop' ? (
|
||||
<iframe srcDoc={markup} className="w-full h-[calc(100vh_-_70px)]" />
|
||||
) : (
|
||||
<div className="flex gap-6 mx-auto p-6 max-w-3xl">
|
||||
<Tooltip.Provider>
|
||||
<CodeContainer
|
||||
markups={[
|
||||
{ language: 'jsx', content: reactMarkup },
|
||||
{ language: 'markup', content: markup },
|
||||
{ language: 'markdown', content: plainText },
|
||||
]}
|
||||
activeLang={activeLang}
|
||||
setActiveLang={handleLangChange}
|
||||
/>
|
||||
</Tooltip.Provider>
|
||||
</div>
|
||||
)}
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user