feat: monorepo

This commit is contained in:
Henry
2024-12-12 14:34:10 +00:00
parent b8eed7a90c
commit 0c8d17dce5
370 changed files with 10280 additions and 39805 deletions

View 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>
);
}

View 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>
);
}

View 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',
};

View 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` };
}

View File

@@ -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>
);
}