diff --git a/.eslintrc.js b/.eslintrc.js index e928b86..ed8d9bb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,10 +1,34 @@ -// This configuration only applies to the package manager root. +const { resolve } = require("node:path"); + +const project = resolve(process.cwd(), "tsconfig.json"); + /** @type {import("eslint").Linter.Config} */ module.exports = { - ignorePatterns: ["apps/**", "packages/**"], - extends: ["@repo/eslint-config/library.js"], - parser: "@typescript-eslint/parser", - parserOptions: { - project: true, + extends: ["eslint:recommended", "prettier"], + plugins: ["only-warn"], + globals: { + React: true, + JSX: true, }, + env: { + node: true, + }, + settings: { + "import/resolver": { + typescript: { + project, + }, + }, + }, + ignorePatterns: [ + // Ignore dotfiles + ".*.js", + "node_modules/", + "dist/", + ], + overrides: [ + { + files: ["*.js?(x)", "*.ts?(x)"], + }, + ], }; diff --git a/.gitignore b/.gitignore index 21894fc..e3f73c5 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,6 @@ out/ build dist - # Debug npm-debug.log* yarn-debug.log* diff --git a/.npmrc b/.npmrc deleted file mode 100644 index ded82e2..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -auto-install-peers = true diff --git a/README.md b/README.md index 54318fc..db8a25f 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# React Email with Turborepo + Bun +# React Email + Turborepo + bun -This example shows how to use React Email with [Turborepo](https://turbo.build) + [Bun](https://bun.sh). +This example shows how to use React Email with [Turborepo](https://turbo.build) + [bun](https://bun.sh/). ### Structure This monorepo includes the following apps: -- `apps/next`: a [Next.js](https://nextjs.org) app -- `apps/react-email`: a [react.email](https://react.email) app +- `apps/web`: a [Next.js](https://nextjs.org) app +- `packages/transactional`: a package with [react.email](https://react.email) email templates ## Instructions @@ -23,11 +23,11 @@ bun install bun dev ``` -4. Open URLs in the browser: +3. Open URL in the browser: * http://localhost:3000 * http://localhost:3001 ## License -MIT License \ No newline at end of file +MIT License diff --git a/apps/next/.eslintrc.js b/apps/next/.eslintrc.js index 7d644a4..f9f9c62 100644 --- a/apps/next/.eslintrc.js +++ b/apps/next/.eslintrc.js @@ -1,9 +1,34 @@ +const { resolve } = require("node:path"); + +const project = resolve(process.cwd(), "tsconfig.json"); + /** @type {import("eslint").Linter.Config} */ module.exports = { - root: true, - extends: ["@repo/eslint-config/next.js"], - parser: "@typescript-eslint/parser", - parserOptions: { - project: true, + extends: [ + "eslint:recommended", + "prettier", + require.resolve("@vercel/style-guide/eslint/next") + ], + parser: '@typescript-eslint/parser', + globals: { + React: true, + JSX: true, }, + env: { + node: true, + }, + plugins: ["only-warn", "@typescript-eslint"], + settings: { + "import/resolver": { + typescript: { + project, + }, + }, + }, + ignorePatterns: [ + // Ignore dotfiles + ".*.js", + "node_modules/", + ], + overrides: [{ files: ["*.js?(x)", "*.ts?(x)"] }], }; diff --git a/apps/next/README.md b/apps/next/README.md index 3d7b63a..5275b7b 100644 --- a/apps/next/README.md +++ b/apps/next/README.md @@ -1,28 +1,17 @@ -## Getting Started +## NextJS Web app -First, run the development server: +A simple example NextJS app that imports the email template from the +[transactional](../../packages/transactional/readme.md) package and renders +it on the index page. -```bash -yarn dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -To create [API routes](https://nextjs.org/docs/app/building-your-application/routing/router-handlers) add an `api/` directory to the `app/` directory with a `route.ts` file. For individual endpoints, create a subfolder in the `api` directory, like `api/hello/route.ts` would map to [http://localhost:3000/api/hello](http://localhost:3000/api/hello). +### Running app -## Learn More +If you want to run the app individually you can just run: -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn/foundations/about-nextjs) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel +```sh +bun dev +``` -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_source=github.com&utm_medium=referral&utm_campaign=turborepo-readme) from the creators of Next.js. +## License -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +MIT License diff --git a/apps/next/app/page.tsx b/apps/next/app/page.tsx index 5925d30..324e83a 100644 --- a/apps/next/app/page.tsx +++ b/apps/next/app/page.tsx @@ -1,5 +1,11 @@ +import { render } from '@react-email/components'; + +import { VercelInviteUserEmail } from 'transactional/emails/vercel-invite-user'; + export default function Page(): JSX.Element { + const emailHTML = render(VercelInviteUserEmail({ })); + return ( -

Next.js App

+
); } diff --git a/apps/next/package.json b/apps/next/package.json index 7d35ef3..fc5ad19 100644 --- a/apps/next/package.json +++ b/apps/next/package.json @@ -9,19 +9,19 @@ "lint": "eslint . --max-warnings 0" }, "dependencies": { + "@react-email/components": "0.0.12", "next": "^14.0.3", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@next/eslint-plugin-next": "^14.0.2", - "@repo/eslint-config": "*", - "@repo/typescript-config": "*", "@types/eslint": "^8.44.7", "@types/node": "^17.0.12", "@types/react": "^18.0.22", "@types/react-dom": "^18.0.7", "eslint": "^8.53.0", + "transactional": "workspace:*", "typescript": "^5.2.2" } } diff --git a/apps/next/tsconfig.json b/apps/next/tsconfig.json index 24e7548..bf0bd21 100644 --- a/apps/next/tsconfig.json +++ b/apps/next/tsconfig.json @@ -1,6 +1,12 @@ { - "extends": "@repo/typescript-config/nextjs.json", + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../tsconfig.json", "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler", + "allowJs": true, + "jsx": "preserve", + "noEmit": true, "plugins": [ { "name": "next" diff --git a/apps/react-email/.react-email/.eslintrc.json b/apps/react-email/.react-email/.eslintrc.json deleted file mode 100644 index 943f99f..0000000 --- a/apps/react-email/.react-email/.eslintrc.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": ["next", "prettier"], - "plugins": ["simple-import-sort", "unused-imports"], - "rules": { - "react/no-unescaped-entities": 0, - "react-hooks/rules-of-hooks": 0, - "no-unused-vars": "off", - "simple-import-sort/imports": [ - "error", - { - // The default grouping, but with no blank lines. - "groups": [["^\\u0000", "^@?\\w", "^", "^\\."]] - } - ], - "simple-import-sort/exports": "error" - } -} \ No newline at end of file diff --git a/apps/react-email/.react-email/.prettierignore b/apps/react-email/.react-email/.prettierignore deleted file mode 100644 index 621285d..0000000 --- a/apps/react-email/.react-email/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ -dist -.next -node_modules \ No newline at end of file diff --git a/apps/react-email/.react-email/.prettierrc.js b/apps/react-email/.react-email/.prettierrc.js deleted file mode 100644 index 14134b0..0000000 --- a/apps/react-email/.react-email/.prettierrc.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - quoteProps: 'consistent', - singleQuote: true, - trailingComma: 'all', - printWidth: 80, - useTabs: false, - bracketSpacing: true, -}; diff --git a/apps/react-email/.react-email/.vscode/settings.json b/apps/react-email/.react-email/.vscode/settings.json deleted file mode 100644 index d067910..0000000 --- a/apps/react-email/.react-email/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "typescript.tsdk": "node_modules/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true -} \ No newline at end of file diff --git a/apps/react-email/.react-email/emails/vercel-invite-user.tsx b/apps/react-email/.react-email/emails/vercel-invite-user.tsx deleted file mode 100644 index 9caa3bf..0000000 --- a/apps/react-email/.react-email/emails/vercel-invite-user.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import Mail from '../../emails/vercel-invite-user.tsx'; -export default Mail; \ No newline at end of file diff --git a/apps/react-email/.react-email/next-env.d.ts b/apps/react-email/.react-email/next-env.d.ts deleted file mode 100644 index 4f11a03..0000000 --- a/apps/react-email/.react-email/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/react-email/.react-email/next.config.js b/apps/react-email/.react-email/next.config.js deleted file mode 100644 index 62cf7fe..0000000 --- a/apps/react-email/.react-email/next.config.js +++ /dev/null @@ -1,11 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: true, - swcMinify: true, - experimental: { - appDir: true, - externalDir: true // compile files that are located next to the .react-email directory - }, -}; - -module.exports = nextConfig; \ No newline at end of file diff --git a/apps/react-email/.react-email/package.json b/apps/react-email/.react-email/package.json deleted file mode 100644 index 27a3c97..0000000 --- a/apps/react-email/.react-email/package.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"react-email-client","version":"0.0.14","description":"The React Email preview application","license":"MIT","scripts":{"dev":"next dev","build":"next build","start":"next start","lint":"next lint","format:check":"prettier --check \"**/*.{ts,tsx,md}\"","format":"prettier --write \"**/*.{ts,tsx,md}\""},"engines":{"node":">=16.0.0"},"dependencies":{"@radix-ui/colors":"0.1.8","@radix-ui/react-collapsible":"1.0.1","@radix-ui/react-popover":"1.0.2","@radix-ui/react-slot":"1.0.1","@radix-ui/react-toggle-group":"1.0.1","@radix-ui/react-tooltip":"1.0.2","@react-email/render":"0.0.7","classnames":"2.3.2","framer-motion":"8.4.6","next":"13.2.4","prism-react-renderer":"1.3.5","react":"18.2.0","react-dom":"18.2.0","@react-email/components":"0.0.6","react-email":"1.9.3"},"devDependencies":{"@types/classnames":"2.3.1","@types/node":"18.11.9","@types/react":"18.0.25","@types/react-dom":"18.0.9","autoprefixer":"10.4.13","eslint":"8.36.0","eslint-config-next":"13.2.4","eslint-config-prettier":"8.7.0","eslint-plugin-simple-import-sort":"10.0.0","eslint-plugin-unused-imports":"2.0.0","postcss":"8.4.19","prettier":"2.8.4","tailwindcss":"3.2.4","typescript":"4.9.3"},"readme":"ERROR: No README data found!","_id":"react-email-client@0.0.14"} \ No newline at end of file diff --git a/apps/react-email/.react-email/postcss.config.js b/apps/react-email/.react-email/postcss.config.js deleted file mode 100644 index 12a703d..0000000 --- a/apps/react-email/.react-email/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/apps/react-email/.react-email/src/app/home.tsx b/apps/react-email/.react-email/src/app/home.tsx deleted file mode 100644 index 130ea73..0000000 --- a/apps/react-email/.react-email/src/app/home.tsx +++ /dev/null @@ -1,25 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import { Button, Heading, Text } from '../components'; -import { Shell } from '../components/shell'; - -export default function Home({ navItems }) { - return ( - -
- - Welcome to the React Email preview! - - - To start developing your next email template, you can create a{' '} - .jsx or .tsx file under the "emails" folder. - - - -
-
- ); -} diff --git a/apps/react-email/.react-email/src/app/layout.tsx b/apps/react-email/.react-email/src/app/layout.tsx deleted file mode 100644 index acfda73..0000000 --- a/apps/react-email/.react-email/src/app/layout.tsx +++ /dev/null @@ -1,24 +0,0 @@ -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 ( - - -
- {children} -
- - - ); -} diff --git a/apps/react-email/.react-email/src/app/page.tsx b/apps/react-email/.react-email/src/app/page.tsx deleted file mode 100644 index 06fe4d5..0000000 --- a/apps/react-email/.react-email/src/app/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { getEmails } from '../utils/get-emails'; -import Home from './home'; - -export default async function Index() { - const { emails } = await getEmails(); - return ; -} - -export const metadata = { - title: 'React Email', -}; diff --git a/apps/react-email/.react-email/src/app/preview/[slug]/page.tsx b/apps/react-email/.react-email/src/app/preview/[slug]/page.tsx deleted file mode 100644 index 6222ca6..0000000 --- a/apps/react-email/.react-email/src/app/preview/[slug]/page.tsx +++ /dev/null @@ -1,56 +0,0 @@ -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(, { pretty: true }); - const plainText = render(, { 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 ( - - ); -} - -export async function generateMetadata({ params }) { - return { title: `${params.slug} — React Email` }; -} diff --git a/apps/react-email/.react-email/src/app/preview/[slug]/preview.tsx b/apps/react-email/.react-email/src/app/preview/[slug]/preview.tsx deleted file mode 100644 index 88caae7..0000000 --- a/apps/react-email/.react-email/src/app/preview/[slug]/preview.tsx +++ /dev/null @@ -1,72 +0,0 @@ -'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 ( - - {activeView === 'desktop' ? ( -