promote-email-templates 0.1.6 → 0.1.8
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/dist/index.d.mts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +27 -3
- package/dist/index.mjs +20 -0
- package/package.json +17 -14
- package/.react-email/.eslintrc.js +0 -52
- package/.react-email/.prettierignore +0 -3
- package/.react-email/.prettierrc.js +0 -8
- package/.react-email/license.md +0 -7
- package/.react-email/next.config.js +0 -36
- package/.react-email/package.json +0 -1
- package/.react-email/postcss.config.js +0 -8
- package/.react-email/readme.md +0 -44
- package/.react-email/src/actions/get-email-path-from-slug.ts +0 -26
- package/.react-email/src/actions/get-emails-directory-metadata.spec.ts +0 -73
- package/.react-email/src/actions/get-emails-directory-metadata.ts +0 -91
- package/.react-email/src/actions/render-email-by-path.tsx +0 -59
- package/.react-email/src/app/favicon.ico +0 -0
- package/.react-email/src/app/globals.css +0 -35
- package/.react-email/src/app/inter.ts +0 -7
- package/.react-email/src/app/layout.tsx +0 -36
- package/.react-email/src/app/logo.png +0 -0
- package/.react-email/src/app/page.tsx +0 -47
- package/.react-email/src/app/preview/[...slug]/page.tsx +0 -65
- package/.react-email/src/app/preview/[...slug]/preview.tsx +0 -141
- package/.react-email/src/app/preview/[...slug]/rendering-error.tsx +0 -40
- package/.react-email/src/components/button.tsx +0 -90
- package/.react-email/src/components/code-container.tsx +0 -145
- package/.react-email/src/components/code.tsx +0 -112
- package/.react-email/src/components/heading.tsx +0 -113
- package/.react-email/src/components/icons/icon-arrow-down.tsx +0 -16
- package/.react-email/src/components/icons/icon-base.tsx +0 -24
- package/.react-email/src/components/icons/icon-button.tsx +0 -23
- package/.react-email/src/components/icons/icon-check.tsx +0 -19
- package/.react-email/src/components/icons/icon-clipboard.tsx +0 -40
- package/.react-email/src/components/icons/icon-download.tsx +0 -19
- package/.react-email/src/components/icons/icon-file.tsx +0 -19
- package/.react-email/src/components/icons/icon-folder-open.tsx +0 -19
- package/.react-email/src/components/icons/icon-folder.tsx +0 -18
- package/.react-email/src/components/icons/icon-hide-sidebar.tsx +0 -23
- package/.react-email/src/components/icons/icon-monitor.tsx +0 -19
- package/.react-email/src/components/icons/icon-phone.tsx +0 -26
- package/.react-email/src/components/icons/icon-source.tsx +0 -19
- package/.react-email/src/components/index.ts +0 -7
- package/.react-email/src/components/logo.tsx +0 -64
- package/.react-email/src/components/send.tsx +0 -135
- package/.react-email/src/components/shell.tsx +0 -115
- package/.react-email/src/components/sidebar/index.ts +0 -1
- package/.react-email/src/components/sidebar/sidebar-directory-children.tsx +0 -134
- package/.react-email/src/components/sidebar/sidebar-directory.tsx +0 -106
- package/.react-email/src/components/sidebar/sidebar.tsx +0 -45
- package/.react-email/src/components/text.tsx +0 -99
- package/.react-email/src/components/tooltip-content.tsx +0 -32
- package/.react-email/src/components/tooltip.tsx +0 -19
- package/.react-email/src/components/topbar.tsx +0 -161
- package/.react-email/src/contexts/emails.tsx +0 -127
- package/.react-email/src/hooks/use-hot-reload.ts +0 -35
- package/.react-email/src/hooks/use-rendering-metadata.ts +0 -36
- package/.react-email/src/utils/cn.ts +0 -6
- package/.react-email/src/utils/constants.ts +0 -6
- package/.react-email/src/utils/copy-text-to-clipboard.ts +0 -7
- package/.react-email/src/utils/emails-directory-absolute-path.ts +0 -34
- package/.react-email/src/utils/get-email-component.ts +0 -108
- package/.react-email/src/utils/improve-error-with-sourcemap.ts +0 -55
- package/.react-email/src/utils/index.ts +0 -5
- package/.react-email/src/utils/language-map.ts +0 -7
- package/.react-email/src/utils/static-node-modules-for-vm.ts +0 -92
- package/.react-email/src/utils/types/as.ts +0 -26
- package/.react-email/src/utils/types/email-template.ts +0 -8
- package/.react-email/src/utils/types/error-object.ts +0 -11
- package/.react-email/src/utils/types/hot-reload-change.ts +0 -6
- package/.react-email/src/utils/types/hot-reload-event.ts +0 -6
- package/.react-email/src/utils/unreachable.ts +0 -8
- package/.react-email/tailwind.config.ts +0 -94
|
@@ -1,91 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,59 +0,0 @@
|
|
|
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
|
|
@@ -1,35 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
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
|
|
@@ -1,47 +0,0 @@
|
|
|
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;
|
|
@@ -1,65 +0,0 @@
|
|
|
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","new-message-notification"]},{"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
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
|
4
|
-
import React from 'react';
|
|
5
|
-
import { Toaster } from 'sonner';
|
|
6
|
-
import { useHotreload } from '../../../hooks/use-hot-reload';
|
|
7
|
-
import type { EmailRenderingResult } from '../../../actions/render-email-by-path';
|
|
8
|
-
import { CodeContainer } from '../../../components/code-container';
|
|
9
|
-
import { Shell } from '../../../components/shell';
|
|
10
|
-
import { Tooltip } from '../../../components/tooltip';
|
|
11
|
-
import { useEmails } from '../../../contexts/emails';
|
|
12
|
-
import { useRenderingMetadata } from '../../../hooks/use-rendering-metadata';
|
|
13
|
-
import { RenderingError } from './rendering-error';
|
|
14
|
-
|
|
15
|
-
interface PreviewProps {
|
|
16
|
-
slug: string;
|
|
17
|
-
emailPath: string;
|
|
18
|
-
renderingResult: EmailRenderingResult;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const Preview = ({
|
|
22
|
-
slug,
|
|
23
|
-
emailPath,
|
|
24
|
-
renderingResult: initialRenderingResult,
|
|
25
|
-
}: PreviewProps) => {
|
|
26
|
-
const router = useRouter();
|
|
27
|
-
const pathname = usePathname();
|
|
28
|
-
const searchParams = useSearchParams();
|
|
29
|
-
|
|
30
|
-
const activeView = searchParams.get('view') ?? 'desktop';
|
|
31
|
-
const activeLang = searchParams.get('lang') ?? 'jsx';
|
|
32
|
-
const { useEmailRenderingResult } = useEmails();
|
|
33
|
-
|
|
34
|
-
const renderingResult = useEmailRenderingResult(
|
|
35
|
-
emailPath,
|
|
36
|
-
initialRenderingResult,
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
const renderedEmailMetadata = useRenderingMetadata(
|
|
40
|
-
emailPath,
|
|
41
|
-
renderingResult,
|
|
42
|
-
initialRenderingResult,
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
if (process.env.NEXT_PUBLIC_IS_BUILDING !== 'true') {
|
|
46
|
-
// this will not change on runtime so it doesn't violate
|
|
47
|
-
// the rules of hooks
|
|
48
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
49
|
-
useHotreload((changes) => {
|
|
50
|
-
const changeForThisEmail = changes.find((change) =>
|
|
51
|
-
change.filename.includes(slug),
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
if (typeof changeForThisEmail !== 'undefined') {
|
|
55
|
-
if (changeForThisEmail.event === 'unlink') {
|
|
56
|
-
router.push('/');
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const handleViewChange = (view: string) => {
|
|
63
|
-
const params = new URLSearchParams(searchParams);
|
|
64
|
-
params.set('view', view);
|
|
65
|
-
router.push(`${pathname}?${params.toString()}`);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const handleLangChange = (lang: string) => {
|
|
69
|
-
const params = new URLSearchParams(searchParams);
|
|
70
|
-
params.set('view', 'source');
|
|
71
|
-
params.set('lang', lang);
|
|
72
|
-
router.push(`${pathname}?${params.toString()}`);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const hasNoErrors = typeof renderedEmailMetadata !== 'undefined';
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<Shell
|
|
79
|
-
activeView={hasNoErrors ? activeView : undefined}
|
|
80
|
-
currentEmailOpenSlug={slug}
|
|
81
|
-
markup={renderedEmailMetadata?.markup}
|
|
82
|
-
setActiveView={hasNoErrors ? handleViewChange : undefined}
|
|
83
|
-
>
|
|
84
|
-
{/* This relative is so that when there is any error the user can still switch between emails */}
|
|
85
|
-
<div className="relative h-full">
|
|
86
|
-
{'error' in renderingResult ? (
|
|
87
|
-
<RenderingError error={renderingResult.error} />
|
|
88
|
-
) : null}
|
|
89
|
-
|
|
90
|
-
{/* If this is undefined means that the initial server render of the email had errors */}
|
|
91
|
-
{hasNoErrors ? (
|
|
92
|
-
<>
|
|
93
|
-
{activeView === 'desktop' && (
|
|
94
|
-
<iframe
|
|
95
|
-
className="w-full bg-white h-[calc(100vh_-_140px)] lg:h-[calc(100vh_-_70px)]"
|
|
96
|
-
srcDoc={renderedEmailMetadata.markup}
|
|
97
|
-
title={slug}
|
|
98
|
-
/>
|
|
99
|
-
)}
|
|
100
|
-
|
|
101
|
-
{activeView === 'mobile' && (
|
|
102
|
-
<iframe
|
|
103
|
-
className="w-[360px] bg-white h-[calc(100vh_-_140px)] lg:h-[calc(100vh_-_70px)] mx-auto"
|
|
104
|
-
srcDoc={renderedEmailMetadata.markup}
|
|
105
|
-
title={slug}
|
|
106
|
-
/>
|
|
107
|
-
)}
|
|
108
|
-
|
|
109
|
-
{activeView === 'source' && (
|
|
110
|
-
<div className="flex gap-6 mx-auto p-6 max-w-3xl">
|
|
111
|
-
<Tooltip.Provider>
|
|
112
|
-
<CodeContainer
|
|
113
|
-
activeLang={activeLang}
|
|
114
|
-
markups={[
|
|
115
|
-
{
|
|
116
|
-
language: 'jsx',
|
|
117
|
-
content: renderedEmailMetadata.reactMarkup,
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
language: 'markup',
|
|
121
|
-
content: renderedEmailMetadata.markup,
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
language: 'markdown',
|
|
125
|
-
content: renderedEmailMetadata.plainText,
|
|
126
|
-
},
|
|
127
|
-
]}
|
|
128
|
-
setActiveLang={handleLangChange}
|
|
129
|
-
/>
|
|
130
|
-
</Tooltip.Provider>
|
|
131
|
-
</div>
|
|
132
|
-
)}
|
|
133
|
-
</>
|
|
134
|
-
) : null}
|
|
135
|
-
<Toaster />
|
|
136
|
-
</div>
|
|
137
|
-
</Shell>
|
|
138
|
-
);
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
export default Preview;
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import type { ErrorObject } from '../../../utils/types/error-object';
|
|
3
|
-
|
|
4
|
-
export const RenderingError = (props: { error: ErrorObject }) => {
|
|
5
|
-
return (
|
|
6
|
-
<>
|
|
7
|
-
<div className="absolute inset-0 z-50 bg-black/80" />
|
|
8
|
-
<div className="md:max-w-[568px] lg:max-w-[968px] absolute left-[50%] top-[50%] min-h-[50vh] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-t-4 bg-white text-black p-6 shadow-lg duration-200 sm:rounded-lg rounded-t-sm">
|
|
9
|
-
<div className="flex flex-col max-w-full min-w-0 space-y-1.5">
|
|
10
|
-
<h2 className="text-lg flex items-center flex-shrink gap-4 font-semibold pb-2 leading-none tracking-tight">
|
|
11
|
-
<svg
|
|
12
|
-
className="h-6 w-6 text-red-600 font-extrabold"
|
|
13
|
-
fill="none"
|
|
14
|
-
height="24"
|
|
15
|
-
stroke="currentColor"
|
|
16
|
-
strokeLinecap="round"
|
|
17
|
-
strokeLinejoin="round"
|
|
18
|
-
strokeWidth="2"
|
|
19
|
-
viewBox="0 0 24 24"
|
|
20
|
-
width="24"
|
|
21
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
22
|
-
>
|
|
23
|
-
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" />
|
|
24
|
-
<path d="M12 9v4" />
|
|
25
|
-
<path d="M12 17h.01" />
|
|
26
|
-
</svg>
|
|
27
|
-
{props.error.name}: {props.error.message}
|
|
28
|
-
</h2>
|
|
29
|
-
{props.error.stack ? (
|
|
30
|
-
<div className="text-sm p-2 flex-grow scroll-px-4 overflow-x-auto bg-red-500 rounded-lg text-gray-100">
|
|
31
|
-
<pre className="font-mono w-full min-w-0 leading-7">
|
|
32
|
-
{props.error.stack}
|
|
33
|
-
</pre>
|
|
34
|
-
</div>
|
|
35
|
-
) : undefined}
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
</>
|
|
39
|
-
);
|
|
40
|
-
};
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import * as SlotPrimitive from '@radix-ui/react-slot';
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { unreachable } from '../utils/unreachable';
|
|
4
|
-
import { cn } from '../utils/cn';
|
|
5
|
-
|
|
6
|
-
type ButtonElement = React.ElementRef<'button'>;
|
|
7
|
-
type RootProps = React.ComponentPropsWithoutRef<'button'>;
|
|
8
|
-
|
|
9
|
-
type Appearance = 'white' | 'gradient';
|
|
10
|
-
type Size = '1' | '2' | '3' | '4';
|
|
11
|
-
|
|
12
|
-
interface ButtonProps extends RootProps {
|
|
13
|
-
asChild?: boolean;
|
|
14
|
-
appearance?: Appearance;
|
|
15
|
-
size?: Size;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const Button = React.forwardRef<ButtonElement, Readonly<ButtonProps>>(
|
|
19
|
-
(
|
|
20
|
-
{
|
|
21
|
-
asChild,
|
|
22
|
-
appearance = 'white',
|
|
23
|
-
className,
|
|
24
|
-
children,
|
|
25
|
-
size = '2',
|
|
26
|
-
...props
|
|
27
|
-
},
|
|
28
|
-
forwardedRef,
|
|
29
|
-
) => {
|
|
30
|
-
const classNames = cn(
|
|
31
|
-
getSize(size),
|
|
32
|
-
getAppearance(appearance),
|
|
33
|
-
'inline-flex items-center justify-center border font-medium',
|
|
34
|
-
className,
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
return asChild ? (
|
|
38
|
-
<SlotPrimitive.Slot ref={forwardedRef} {...props} className={classNames}>
|
|
39
|
-
<SlotPrimitive.Slottable>{children}</SlotPrimitive.Slottable>
|
|
40
|
-
</SlotPrimitive.Slot>
|
|
41
|
-
) : (
|
|
42
|
-
<button
|
|
43
|
-
className={classNames}
|
|
44
|
-
ref={forwardedRef}
|
|
45
|
-
type="button"
|
|
46
|
-
{...props}
|
|
47
|
-
>
|
|
48
|
-
{children}
|
|
49
|
-
</button>
|
|
50
|
-
);
|
|
51
|
-
},
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
Button.displayName = 'Button';
|
|
55
|
-
|
|
56
|
-
const getAppearance = (appearance: Appearance | undefined) => {
|
|
57
|
-
switch (appearance) {
|
|
58
|
-
case undefined:
|
|
59
|
-
case 'white':
|
|
60
|
-
return [
|
|
61
|
-
'bg-white text-black',
|
|
62
|
-
'hover:bg-white/90',
|
|
63
|
-
'focus:ring-2 focus:ring-white/20 focus:outline-none focus:bg-white/90',
|
|
64
|
-
];
|
|
65
|
-
case 'gradient':
|
|
66
|
-
return [
|
|
67
|
-
'bg-gradient backdrop-blur-[20px] border-[#34343A]',
|
|
68
|
-
'hover:bg-gradientHover',
|
|
69
|
-
'focus:ring-2 focus:ring-white/20 focus:outline-none focus:bg-gradientHover',
|
|
70
|
-
];
|
|
71
|
-
default:
|
|
72
|
-
unreachable(appearance);
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const getSize = (size: Size | undefined) => {
|
|
77
|
-
switch (size) {
|
|
78
|
-
case '1':
|
|
79
|
-
return '';
|
|
80
|
-
case undefined:
|
|
81
|
-
case '2':
|
|
82
|
-
return 'text-[14px] h-8 px-3 rounded-md gap-2';
|
|
83
|
-
case '3':
|
|
84
|
-
return 'text-[14px] h-10 px-4 rounded-md gap-2';
|
|
85
|
-
case '4':
|
|
86
|
-
return 'text-base h-11 px-4 rounded-md gap-2';
|
|
87
|
-
default:
|
|
88
|
-
unreachable(size);
|
|
89
|
-
}
|
|
90
|
-
};
|