react-email 4.0.0-alpha.2 → 4.0.0-alpha.4
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 +12 -0
- package/dist/cli/index.js +3 -2
- package/dist/cli/index.mjs +3 -2
- package/dist/preview/.next/BUILD_ID +1 -1
- package/dist/preview/.next/app-build-manifest.json +10 -10
- package/dist/preview/.next/build-manifest.json +3 -3
- package/dist/preview/.next/cache/.rscinfo +1 -1
- package/dist/preview/.next/cache/webpack/client-production/0.pack +0 -0
- package/dist/preview/.next/cache/webpack/client-production/index.pack +0 -0
- package/dist/preview/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/dist/preview/.next/cache/webpack/server-production/0.pack +0 -0
- package/dist/preview/.next/cache/webpack/server-production/index.pack +0 -0
- package/dist/preview/.next/next-minimal-server.js.nft.json +1 -1
- package/dist/preview/.next/next-server.js.nft.json +1 -1
- package/dist/preview/.next/prerender-manifest.json +1 -1
- package/dist/preview/.next/required-server-files.json +1 -1
- package/dist/preview/.next/server/app/_not-found/page.js +1 -1
- package/dist/preview/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/preview/.next/server/app/favicon.ico/route.js +1 -1
- package/dist/preview/.next/server/app/page.js +1 -1
- package/dist/preview/.next/server/app/page.js.nft.json +1 -1
- package/dist/preview/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/preview/.next/server/app/preview/[...slug]/page.js +5 -5
- package/dist/preview/.next/server/app/preview/[...slug]/page.js.nft.json +1 -1
- package/dist/preview/.next/server/app/preview/[...slug]/page_client-reference-manifest.js +1 -1
- package/dist/preview/.next/server/chunks/196.js +2 -2
- package/dist/preview/.next/server/chunks/631.js +2 -2
- package/dist/preview/.next/server/chunks/644.js +1 -0
- package/dist/preview/.next/server/chunks/734.js +1 -1
- package/dist/preview/.next/server/middleware-build-manifest.js +1 -1
- package/dist/preview/.next/server/next-font-manifest.js +1 -1
- package/dist/preview/.next/server/next-font-manifest.json +1 -1
- package/dist/preview/.next/server/pages/500.html +1 -1
- package/dist/preview/.next/server/server-reference-manifest.js +1 -1
- package/dist/preview/.next/server/server-reference-manifest.json +1 -1
- package/dist/preview/.next/static/chunks/490-d5745684930d49e0.js +1 -0
- package/dist/preview/.next/static/chunks/app/layout-d06046b8a368df3b.js +1 -0
- package/dist/preview/.next/static/chunks/app/page-ef1c23b954fbd0b5.js +1 -0
- package/dist/preview/.next/static/chunks/app/preview/[...slug]/{page-9906dc842681db05.js → page-ea8e1ae2b5a4a0ec.js} +1 -1
- package/dist/preview/.next/static/chunks/{main-app-d1b0aa870bcfb13e.js → main-app-9f2fb5ea26e2765b.js} +1 -1
- package/dist/preview/.next/static/css/e4822d5ba3082a95.css +3 -0
- package/dist/preview/.next/trace +22 -22
- package/dist/preview/.next/types/app/layout.ts +1 -1
- package/dist/preview/.next/types/app/preview/[...slug]/page.ts +1 -1
- package/package.json +3 -2
- package/src/app/preview/[...slug]/preview.tsx +25 -22
- package/src/components/icons/icon-bug.tsx +19 -0
- package/src/components/shell.tsx +3 -0
- package/src/components/sidebar/file-tree-directory-children.tsx +15 -12
- package/src/components/sidebar/image-checker.tsx +2 -1
- package/src/components/sidebar/sidebar.tsx +36 -2
- package/src/components/sidebar/spam-assassin.tsx +158 -0
- package/src/utils/__snapshots__/get-email-component.spec.ts.snap +1 -1
- package/tsconfig.json +1 -1
- package/vitest.config.ts +5 -3
- package/dist/preview/.next/server/chunks/590.js +0 -1
- package/dist/preview/.next/static/chunks/490-d26ba2019ccd4d2f.js +0 -1
- package/dist/preview/.next/static/chunks/app/layout-b13c19549e2d3e57.js +0 -1
- package/dist/preview/.next/static/chunks/app/page-8f366f3c14282f33.js +0 -1
- package/dist/preview/.next/static/css/b60917edfd15a496.css +0 -3
- package/tsconfig.test.json +0 -8
- /package/dist/preview/.next/static/{ll_lhpCErxdDFU8uF5Ujy → Pt6wqIrWnQxbiyqaKNFOx}/_buildManifest.js +0 -0
- /package/dist/preview/.next/static/{ll_lhpCErxdDFU8uF5Ujy → Pt6wqIrWnQxbiyqaKNFOx}/_ssgManifest.js +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// File: /home/gabriel/Projects/Resend/react-email.git/
|
|
1
|
+
// File: /home/gabriel/Projects/Resend/react-email.git/4.0/packages/react-email/src/app/layout.tsx
|
|
2
2
|
import * as entry from '../../../src/app/layout.js'
|
|
3
3
|
import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// File: /home/gabriel/Projects/Resend/react-email.git/
|
|
1
|
+
// File: /home/gabriel/Projects/Resend/react-email.git/4.0/packages/react-email/src/app/preview/[...slug]/page.tsx
|
|
2
2
|
import * as entry from '../../../../../src/app/preview/[...slug]/page.js'
|
|
3
3
|
import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
|
|
4
4
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-email",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
3
|
+
"version": "4.0.0-alpha.4",
|
|
4
4
|
"description": "A live preview of your emails right in your browser.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"email": "./dist/cli/index.js"
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"socket.io": "4.8.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
+
"@lottiefiles/dotlottie-react": "0.12.3",
|
|
38
39
|
"@radix-ui/colors": "1.0.1",
|
|
39
40
|
"@radix-ui/react-collapsible": "1.1.0",
|
|
40
41
|
"@radix-ui/react-dropdown-menu": "2.1.4",
|
|
@@ -57,7 +58,6 @@
|
|
|
57
58
|
"autoprefixer": "10.4.20",
|
|
58
59
|
"clsx": "2.1.0",
|
|
59
60
|
"framer-motion": "12.0.0-alpha.2",
|
|
60
|
-
"@lottiefiles/dotlottie-react": "0.12.3",
|
|
61
61
|
"node-html-parser": "6.1.13",
|
|
62
62
|
"postcss": "8.4.40",
|
|
63
63
|
"prettier-plugin-tailwindcss": "0.6.6",
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
"socket.io-client": "4.8.0",
|
|
71
71
|
"sonner": "1.7.1",
|
|
72
72
|
"source-map-js": "1.0.2",
|
|
73
|
+
"spamc": "0.0.5",
|
|
73
74
|
"stacktrace-parser": "0.1.10",
|
|
74
75
|
"tailwind-merge": "2.2.0",
|
|
75
76
|
"tailwindcss": "3.4.0",
|
|
@@ -111,6 +111,7 @@ const Preview = ({
|
|
|
111
111
|
activeView={activeView}
|
|
112
112
|
currentEmailOpenSlug={slug}
|
|
113
113
|
markup={renderedEmailMetadata?.markup}
|
|
114
|
+
plainText={renderedEmailMetadata?.plainText}
|
|
114
115
|
pathSeparator={pathSeparator}
|
|
115
116
|
setActiveView={handleViewChange}
|
|
116
117
|
setViewHeight={(height) => {
|
|
@@ -130,7 +131,7 @@ const Preview = ({
|
|
|
130
131
|
>
|
|
131
132
|
{/* This relative is so that when there is any error the user can still switch between emails */}
|
|
132
133
|
<div
|
|
133
|
-
className="relative flex h-full
|
|
134
|
+
className="relative flex h-full bg-gray-200 pb-8"
|
|
134
135
|
ref={(element) => {
|
|
135
136
|
const observer = new ResizeObserver((entry) => {
|
|
136
137
|
const [elementEntry] = entry;
|
|
@@ -194,27 +195,29 @@ const Preview = ({
|
|
|
194
195
|
)}
|
|
195
196
|
|
|
196
197
|
{activeView === 'source' && (
|
|
197
|
-
<div className="
|
|
198
|
-
<
|
|
199
|
-
<
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
{
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
198
|
+
<div className="h-full w-full bg-black">
|
|
199
|
+
<div className="m-auto flex max-w-3xl p-6">
|
|
200
|
+
<Tooltip.Provider>
|
|
201
|
+
<CodeContainer
|
|
202
|
+
activeLang={activeLang}
|
|
203
|
+
markups={[
|
|
204
|
+
{
|
|
205
|
+
language: 'jsx',
|
|
206
|
+
content: renderedEmailMetadata.reactMarkup,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
language: 'markup',
|
|
210
|
+
content: renderedEmailMetadata.markup,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
language: 'markdown',
|
|
214
|
+
content: renderedEmailMetadata.plainText,
|
|
215
|
+
},
|
|
216
|
+
]}
|
|
217
|
+
setActiveLang={handleLangChange}
|
|
218
|
+
/>
|
|
219
|
+
</Tooltip.Provider>
|
|
220
|
+
</div>
|
|
218
221
|
</div>
|
|
219
222
|
)}
|
|
220
223
|
</>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import type { IconElement, IconProps } from './icon-base';
|
|
3
|
+
import { IconBase } from './icon-base';
|
|
4
|
+
|
|
5
|
+
export const IconBug = forwardRef<IconElement, IconProps>((props, ref) => (
|
|
6
|
+
<IconBase {...props} ref={ref}>
|
|
7
|
+
<g
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
strokeLinecap="round"
|
|
11
|
+
strokeLinejoin="round"
|
|
12
|
+
strokeWidth="2"
|
|
13
|
+
>
|
|
14
|
+
<path d="m8 2l1.88 1.88m4.24 0L16 2M9 7.13v-1a3.003 3.003 0 1 1 6 0v1" />
|
|
15
|
+
<path d="M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6m0 0v-9" />
|
|
16
|
+
<path d="M6.53 9C4.6 8.8 3 7.1 3 5m3 8H2m1 8c0-2.1 1.7-3.9 3.8-4M20.97 5c0 2.1-1.6 3.8-3.5 4M22 13h-4m-.8 4c2.1.1 3.8 1.9 3.8 4" />
|
|
17
|
+
</g>
|
|
18
|
+
</IconBase>
|
|
19
|
+
));
|
package/src/components/shell.tsx
CHANGED
|
@@ -10,6 +10,7 @@ type RootProps = React.ComponentPropsWithoutRef<'div'>;
|
|
|
10
10
|
|
|
11
11
|
interface ShellProps extends RootProps {
|
|
12
12
|
markup?: string;
|
|
13
|
+
plainText?: string;
|
|
13
14
|
currentEmailOpenSlug?: string;
|
|
14
15
|
pathSeparator?: string;
|
|
15
16
|
|
|
@@ -27,6 +28,7 @@ export const Shell = ({
|
|
|
27
28
|
children,
|
|
28
29
|
pathSeparator,
|
|
29
30
|
markup,
|
|
31
|
+
plainText,
|
|
30
32
|
activeView,
|
|
31
33
|
setActiveView,
|
|
32
34
|
viewHeight,
|
|
@@ -76,6 +78,7 @@ export const Shell = ({
|
|
|
76
78
|
})}
|
|
77
79
|
currentEmailOpenSlug={currentEmailOpenSlug}
|
|
78
80
|
markup={markup}
|
|
81
|
+
plainText={plainText}
|
|
79
82
|
style={{
|
|
80
83
|
transition: triggerTransition ? 'transform 0.2s ease-in-out' : '',
|
|
81
84
|
}}
|
|
@@ -76,7 +76,8 @@ export const FileTreeDirectoryChildren = (props: {
|
|
|
76
76
|
<motion.span
|
|
77
77
|
animate={{ x: 0, opacity: 1 }}
|
|
78
78
|
className={cn(
|
|
79
|
-
'relative flex h-8 max-w-full items-center rounded-md
|
|
79
|
+
'relative flex h-8 max-w-full items-center gap-2 rounded-md align-middle text-slate-11 text-sm transition-colors duration-100 ease-[cubic-bezier(.6,.12,.34,.96)]',
|
|
80
|
+
props.isRoot ? undefined : 'pl-3',
|
|
80
81
|
{
|
|
81
82
|
'text-cyan-11': isCurrentPage,
|
|
82
83
|
'hover:text-slate-12':
|
|
@@ -96,23 +97,25 @@ export const FileTreeDirectoryChildren = (props: {
|
|
|
96
97
|
exit={{ opacity: 0 }}
|
|
97
98
|
initial={{ opacity: 0 }}
|
|
98
99
|
>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
{props.isRoot ? null : (
|
|
101
|
+
<motion.div
|
|
102
|
+
className="absolute top-1 left-[.625rem] h-6 w-px rounded-sm bg-cyan-11"
|
|
103
|
+
layoutId="active-file"
|
|
104
|
+
transition={{
|
|
105
|
+
type: 'spring',
|
|
106
|
+
bounce: 0.2,
|
|
107
|
+
duration: 0.6,
|
|
108
|
+
}}
|
|
109
|
+
/>
|
|
110
|
+
)}
|
|
108
111
|
</motion.span>
|
|
109
112
|
) : null}
|
|
110
113
|
<IconFile
|
|
111
|
-
className="
|
|
114
|
+
className="h-5 w-5"
|
|
112
115
|
height="20"
|
|
113
116
|
width="20"
|
|
114
117
|
/>
|
|
115
|
-
<span className="truncate
|
|
118
|
+
<span className="truncate">{emailFilename}</span>
|
|
116
119
|
</motion.span>
|
|
117
120
|
</Link>
|
|
118
121
|
);
|
|
@@ -150,7 +150,8 @@ export const ImageChecker = ({ emailSlug, emailMarkup }: ImageCheckerProps) => {
|
|
|
150
150
|
</>
|
|
151
151
|
) : (
|
|
152
152
|
<span className="text-xs leading-relaxed">
|
|
153
|
-
Check if all
|
|
153
|
+
Check if all images exist, have proper size, are accessible, and are
|
|
154
|
+
secure.
|
|
154
155
|
</span>
|
|
155
156
|
)}
|
|
156
157
|
<Button loading={loading} onClick={handleRun}>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
|
|
3
2
|
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
|
|
4
3
|
import * as Tabs from '@radix-ui/react-tabs';
|
|
5
4
|
import { clsx } from 'clsx';
|
|
@@ -15,18 +14,25 @@ import { useIconAnimation } from '../../hooks/use-icon-animation';
|
|
|
15
14
|
import { cn } from '../../utils';
|
|
16
15
|
import { Button } from '../button';
|
|
17
16
|
import { Heading } from '../heading';
|
|
17
|
+
import { IconBug } from '../icons/icon-bug';
|
|
18
18
|
import { IconImage } from '../icons/icon-image';
|
|
19
19
|
import { Tooltip } from '../tooltip';
|
|
20
20
|
import { FileTree } from './file-tree';
|
|
21
21
|
import { ImageChecker } from './image-checker';
|
|
22
22
|
import { LinkChecker } from './link-checker';
|
|
23
|
+
import { SpamAssassin } from './spam-assassin';
|
|
23
24
|
|
|
24
|
-
type SidebarPanelValue =
|
|
25
|
+
type SidebarPanelValue =
|
|
26
|
+
| 'file-tree'
|
|
27
|
+
| 'link-checker'
|
|
28
|
+
| 'image-checker'
|
|
29
|
+
| 'spam-assassin';
|
|
25
30
|
|
|
26
31
|
interface SidebarProps {
|
|
27
32
|
className?: string;
|
|
28
33
|
currentEmailOpenSlug?: string;
|
|
29
34
|
markup?: string;
|
|
35
|
+
plainText?: string;
|
|
30
36
|
style?: React.CSSProperties;
|
|
31
37
|
}
|
|
32
38
|
|
|
@@ -179,6 +185,7 @@ export const Sidebar = ({
|
|
|
179
185
|
className,
|
|
180
186
|
currentEmailOpenSlug,
|
|
181
187
|
markup: emailMarkup,
|
|
188
|
+
plainText: emailPlainText,
|
|
182
189
|
style,
|
|
183
190
|
}: SidebarProps) => {
|
|
184
191
|
const pathname = usePathname();
|
|
@@ -256,6 +263,14 @@ export const Sidebar = ({
|
|
|
256
263
|
>
|
|
257
264
|
<IconImage className="h-6 w-6" />
|
|
258
265
|
</TabTrigger>
|
|
266
|
+
<TabTrigger
|
|
267
|
+
activeTabValue={activePanelValue}
|
|
268
|
+
className="relative"
|
|
269
|
+
tabValue="spam-assassin"
|
|
270
|
+
tooltipText="Spam Assassin"
|
|
271
|
+
>
|
|
272
|
+
<IconBug className="h-6 w-6" />
|
|
273
|
+
</TabTrigger>
|
|
259
274
|
<div className="mt-auto flex flex-col">
|
|
260
275
|
<NavigationButton
|
|
261
276
|
className="flex items-center justify-center"
|
|
@@ -325,6 +340,25 @@ export const Sidebar = ({
|
|
|
325
340
|
)}
|
|
326
341
|
</Panel>
|
|
327
342
|
)}
|
|
343
|
+
{activePanelValue === 'spam-assassin' && (
|
|
344
|
+
<Panel
|
|
345
|
+
title="Image Checker"
|
|
346
|
+
active={activePanelValue === 'spam-assassin'}
|
|
347
|
+
>
|
|
348
|
+
{currentEmailOpenSlug && emailMarkup && emailPlainText ? (
|
|
349
|
+
<SpamAssassin
|
|
350
|
+
emailMarkup={emailMarkup}
|
|
351
|
+
emailPlainText={emailPlainText}
|
|
352
|
+
emailSlug={currentEmailOpenSlug}
|
|
353
|
+
/>
|
|
354
|
+
) : (
|
|
355
|
+
<EmptyState
|
|
356
|
+
title="Spam Assassin"
|
|
357
|
+
onSelectTemplate={() => setActivePanelValue('file-tree')}
|
|
358
|
+
/>
|
|
359
|
+
)}
|
|
360
|
+
</Panel>
|
|
361
|
+
)}
|
|
328
362
|
{activePanelValue === 'file-tree' && (
|
|
329
363
|
<Panel
|
|
330
364
|
title="File Explorer"
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { toast } from 'sonner';
|
|
3
|
+
import { cn } from '../../utils';
|
|
4
|
+
import { Button } from '../button';
|
|
5
|
+
|
|
6
|
+
interface SpamAssassinProps {
|
|
7
|
+
emailSlug: string;
|
|
8
|
+
emailMarkup: string;
|
|
9
|
+
emailPlainText: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface SpamCheckingResult {
|
|
13
|
+
checks: {
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
points: number;
|
|
17
|
+
}[];
|
|
18
|
+
isSpam: boolean;
|
|
19
|
+
points: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const SpamAssassin = ({
|
|
23
|
+
emailSlug,
|
|
24
|
+
emailMarkup,
|
|
25
|
+
emailPlainText,
|
|
26
|
+
}: SpamAssassinProps) => {
|
|
27
|
+
const cacheKey = `spam-checking-results-${emailSlug.replaceAll('/', '-')}`;
|
|
28
|
+
|
|
29
|
+
const [result, setResult] = React.useState<SpamCheckingResult | undefined>();
|
|
30
|
+
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
const cachedValue =
|
|
33
|
+
'localStorage' in global ? global.localStorage.getItem(cacheKey) : null;
|
|
34
|
+
if (cachedValue) {
|
|
35
|
+
try {
|
|
36
|
+
setResult(JSON.parse(cachedValue));
|
|
37
|
+
} catch (exception) {
|
|
38
|
+
setResult(undefined);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}, [cacheKey]);
|
|
42
|
+
|
|
43
|
+
const [loading, setLoading] = React.useState(false);
|
|
44
|
+
|
|
45
|
+
const handleRun = async () => {
|
|
46
|
+
setLoading(true);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const response = await fetch('https://react.email/api/check-spam', {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: { 'Content-Type': 'application/json' },
|
|
52
|
+
body: JSON.stringify({
|
|
53
|
+
html: emailMarkup,
|
|
54
|
+
plainText: emailPlainText,
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (response.ok) {
|
|
59
|
+
const responseBody = (await response.json()) as
|
|
60
|
+
| { error: string }
|
|
61
|
+
| SpamCheckingResult;
|
|
62
|
+
if ('error' in responseBody) {
|
|
63
|
+
toast.error(responseBody.error);
|
|
64
|
+
} else {
|
|
65
|
+
setResult(responseBody);
|
|
66
|
+
localStorage.setItem(cacheKey, JSON.stringify(responseBody));
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
console.error(await response.text());
|
|
70
|
+
toast.error('Something went wrong');
|
|
71
|
+
}
|
|
72
|
+
} catch (exception) {
|
|
73
|
+
console.error(exception);
|
|
74
|
+
toast.error(JSON.stringify(exception));
|
|
75
|
+
} finally {
|
|
76
|
+
setLoading(false);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div className="mt-4 flex w-full flex-col gap-2 text-pretty">
|
|
82
|
+
{result ? (
|
|
83
|
+
<div
|
|
84
|
+
className="group flex flex-col gap-2"
|
|
85
|
+
aria-label={result.isSpam ? 'spam' : 'ham'}
|
|
86
|
+
>
|
|
87
|
+
<table className="w-full border-collapse text-left text-slate-10 text-sm">
|
|
88
|
+
<thead className="mb-4 h-8 border border-t-0 border-slate-6 bg-slate-3 text-xs">
|
|
89
|
+
<tr>
|
|
90
|
+
<th scope="col" className="px-3 py-1">
|
|
91
|
+
Rule
|
|
92
|
+
</th>
|
|
93
|
+
<th scope="col" className="px-3 py-1" align="right">
|
|
94
|
+
Score
|
|
95
|
+
</th>
|
|
96
|
+
</tr>
|
|
97
|
+
</thead>
|
|
98
|
+
<tbody>
|
|
99
|
+
{result.checks.length > 0 ? (
|
|
100
|
+
result.checks.map((check) => (
|
|
101
|
+
<tr
|
|
102
|
+
key={check.name}
|
|
103
|
+
className="border-collapse border-slate-6 border-b"
|
|
104
|
+
>
|
|
105
|
+
<td className="px-3 py-2">
|
|
106
|
+
<div className="font-medium text-slate-12">
|
|
107
|
+
{check.name}
|
|
108
|
+
</div>
|
|
109
|
+
<div className="mt-1 text-slate-9 text-xs">
|
|
110
|
+
{check.description}
|
|
111
|
+
</div>
|
|
112
|
+
</td>
|
|
113
|
+
<td
|
|
114
|
+
align="right"
|
|
115
|
+
className={cn(
|
|
116
|
+
'px-3 py-2 font-medium',
|
|
117
|
+
check.points > 0 ? 'text-yellow-200' : null,
|
|
118
|
+
check.points > 1 ? 'text-yellow-300' : null,
|
|
119
|
+
check.points > 2 ? 'text-orange-400' : null,
|
|
120
|
+
check.points > 3 ? 'text-red-400' : null,
|
|
121
|
+
)}
|
|
122
|
+
>
|
|
123
|
+
{check.points.toFixed(1)}
|
|
124
|
+
</td>
|
|
125
|
+
</tr>
|
|
126
|
+
))
|
|
127
|
+
) : (
|
|
128
|
+
<tr>
|
|
129
|
+
<td colSpan={2} className="py-10 font-medium text-center">
|
|
130
|
+
Nothing from Spam Assassin
|
|
131
|
+
</td>
|
|
132
|
+
</tr>
|
|
133
|
+
)}
|
|
134
|
+
</tbody>
|
|
135
|
+
<tfoot className="mb-4 h-8 border border-slate-6 bg-slate-3">
|
|
136
|
+
<tr className="border-collapse border-slate-6 border-b">
|
|
137
|
+
<td className="px-3 py-2">Considered </td>
|
|
138
|
+
<td
|
|
139
|
+
className="text-green-300 py-2 px-3 group-aria-[label=spam]:text-red-300 bg-transparent"
|
|
140
|
+
align="right"
|
|
141
|
+
>
|
|
142
|
+
{result.isSpam ? 'spam' : 'safe'}
|
|
143
|
+
</td>
|
|
144
|
+
</tr>
|
|
145
|
+
</tfoot>
|
|
146
|
+
</table>
|
|
147
|
+
</div>
|
|
148
|
+
) : (
|
|
149
|
+
<span className="text-xs leading-relaxed">
|
|
150
|
+
Check how well your email goes on a batch of spam testing.
|
|
151
|
+
</span>
|
|
152
|
+
)}
|
|
153
|
+
<Button loading={loading} onClick={handleRun}>
|
|
154
|
+
{result ? 'Re-run' : 'Run'}
|
|
155
|
+
</Button>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
|
-
exports[`getEmailComponent() > with a demo email template 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" lang="en"><head><link rel="preload" as="image" href="/static/vercel-logo.png"/><link rel="preload" as="image" href="/static/vercel-user.png"/><link rel="preload" as="image" href="/static/vercel-arrow.png"/><link rel="preload" as="image" href="/static/vercel-team.png"/><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><!--$--></head><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0">Join Alan on Vercel<div> </div></div><
|
|
3
|
+
exports[`getEmailComponent() > with a demo email template 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" lang="en"><head><link rel="preload" as="image" href="/static/vercel-logo.png"/><link rel="preload" as="image" href="/static/vercel-user.png"/><link rel="preload" as="image" href="/static/vercel-arrow.png"/><link rel="preload" as="image" href="/static/vercel-team.png"/><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><!--$--></head><body style="background-color:rgb(255,255,255);margin-top:auto;margin-bottom:auto;margin-left:auto;margin-right:auto;font-family:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";padding-left:0.5rem;padding-right:0.5rem"><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0">Join Alan on Vercel<div> </div></div><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="border-width:1px;border-style:solid;border-color:rgb(234,234,234);border-radius:0.25rem;margin-top:40px;margin-bottom:40px;margin-left:auto;margin-right:auto;padding:20px;max-width:465px"><tbody><tr style="width:100%"><td><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="margin-top:32px"><tbody><tr><td><img alt="Vercel" height="37" src="/static/vercel-logo.png" style="margin-top:0px;margin-bottom:0px;margin-left:auto;margin-right:auto;display:block;outline:none;border:none;text-decoration:none" width="40"/></td></tr></tbody></table><h1 style="color:rgb(0,0,0);font-size:24px;font-weight:400;text-align:center;padding:0px;margin-top:30px;margin-bottom:30px;margin-left:0px;margin-right:0px">Join <strong>Enigma</strong> on <strong>Vercel</strong></h1><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin:16px 0">Hello <!-- -->alanturing<!-- -->,</p><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin:16px 0"><strong>Alan</strong> (<a href="mailto:alan.turing@example.com" style="color:rgb(37,99,235);text-decoration-line:none" target="_blank">alan.turing@example.com</a>) has invited you to the <strong>Enigma</strong> team on<!-- --> <strong>Vercel</strong>.</p><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation"><tbody><tr><td><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation"><tbody style="width:100%"><tr style="width:100%"><td align="right" data-id="__react-email-column"><img height="64" src="/static/vercel-user.png" style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none" width="64"/></td><td align="center" data-id="__react-email-column"><img alt="invited you to" height="9" src="/static/vercel-arrow.png" style="display:block;outline:none;border:none;text-decoration:none" width="12"/></td><td align="left" data-id="__react-email-column"><img height="64" src="/static/vercel-team.png" style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none" width="64"/></td></tr></tbody></table></td></tr></tbody></table><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="text-align:center;margin-top:32px;margin-bottom:32px"><tbody><tr><td><a href="https://vercel.com/teams/invite/foo" style="background-color:rgb(0,0,0);border-radius:0.25rem;color:rgb(255,255,255);font-size:12px;font-weight:600;text-decoration-line:none;text-align:center;padding-left:1.25rem;padding-right:1.25rem;padding-top:0.75rem;padding-bottom:0.75rem;line-height:100%;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;padding:12px 20px 12px 20px" target="_blank"><span><!--[if mso]><i style="mso-font-width:500%;mso-text-raise:18" hidden>  </i><![endif]--></span><span style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:9px">Join the team</span><span><!--[if mso]><i style="mso-font-width:500%" hidden>  ​</i><![endif]--></span></a></td></tr></tbody></table><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin:16px 0">or copy and paste this URL into your browser:<!-- --> <a href="https://vercel.com/teams/invite/foo" style="color:rgb(37,99,235);text-decoration-line:none" target="_blank">https://vercel.com/teams/invite/foo</a></p><hr style="border-width:1px;border-style:solid;border-color:rgb(234,234,234);margin-top:26px;margin-bottom:26px;margin-left:0px;margin-right:0px;width:100%;border:none;border-top:1px solid #eaeaea"/><p style="color:rgb(102,102,102);font-size:12px;line-height:24px;margin:16px 0">This invitation was intended for<!-- --> <span style="color:rgb(0,0,0)">alanturing</span>. This invite was sent from <span style="color:rgb(0,0,0)">204.13.186.218</span> <!-- -->located in<!-- --> <span style="color:rgb(0,0,0)">São Paulo, Brazil</span>. If you were not expecting this invitation, you can ignore this email. If you are concerned about your account's safety, please reply to this email to get in touch with us.</p></td></tr></tbody></table><!--/$--></body></html>"`;
|
package/tsconfig.json
CHANGED
package/vitest.config.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import type { TsconfigRaw } from 'esbuild';
|
|
2
1
|
import { defineConfig } from 'vitest/config';
|
|
3
|
-
import tsconfig from './tsconfig.test.json';
|
|
4
2
|
|
|
5
3
|
export default defineConfig({
|
|
6
4
|
test: {
|
|
@@ -8,6 +6,10 @@ export default defineConfig({
|
|
|
8
6
|
environment: 'happy-dom',
|
|
9
7
|
},
|
|
10
8
|
esbuild: {
|
|
11
|
-
tsconfigRaw:
|
|
9
|
+
tsconfigRaw: {
|
|
10
|
+
compilerOptions: {
|
|
11
|
+
jsx: 'react-jsx',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
12
14
|
},
|
|
13
15
|
});
|