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.
Files changed (63) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cli/index.js +3 -2
  3. package/dist/cli/index.mjs +3 -2
  4. package/dist/preview/.next/BUILD_ID +1 -1
  5. package/dist/preview/.next/app-build-manifest.json +10 -10
  6. package/dist/preview/.next/build-manifest.json +3 -3
  7. package/dist/preview/.next/cache/.rscinfo +1 -1
  8. package/dist/preview/.next/cache/webpack/client-production/0.pack +0 -0
  9. package/dist/preview/.next/cache/webpack/client-production/index.pack +0 -0
  10. package/dist/preview/.next/cache/webpack/edge-server-production/index.pack +0 -0
  11. package/dist/preview/.next/cache/webpack/server-production/0.pack +0 -0
  12. package/dist/preview/.next/cache/webpack/server-production/index.pack +0 -0
  13. package/dist/preview/.next/next-minimal-server.js.nft.json +1 -1
  14. package/dist/preview/.next/next-server.js.nft.json +1 -1
  15. package/dist/preview/.next/prerender-manifest.json +1 -1
  16. package/dist/preview/.next/required-server-files.json +1 -1
  17. package/dist/preview/.next/server/app/_not-found/page.js +1 -1
  18. package/dist/preview/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  19. package/dist/preview/.next/server/app/favicon.ico/route.js +1 -1
  20. package/dist/preview/.next/server/app/page.js +1 -1
  21. package/dist/preview/.next/server/app/page.js.nft.json +1 -1
  22. package/dist/preview/.next/server/app/page_client-reference-manifest.js +1 -1
  23. package/dist/preview/.next/server/app/preview/[...slug]/page.js +5 -5
  24. package/dist/preview/.next/server/app/preview/[...slug]/page.js.nft.json +1 -1
  25. package/dist/preview/.next/server/app/preview/[...slug]/page_client-reference-manifest.js +1 -1
  26. package/dist/preview/.next/server/chunks/196.js +2 -2
  27. package/dist/preview/.next/server/chunks/631.js +2 -2
  28. package/dist/preview/.next/server/chunks/644.js +1 -0
  29. package/dist/preview/.next/server/chunks/734.js +1 -1
  30. package/dist/preview/.next/server/middleware-build-manifest.js +1 -1
  31. package/dist/preview/.next/server/next-font-manifest.js +1 -1
  32. package/dist/preview/.next/server/next-font-manifest.json +1 -1
  33. package/dist/preview/.next/server/pages/500.html +1 -1
  34. package/dist/preview/.next/server/server-reference-manifest.js +1 -1
  35. package/dist/preview/.next/server/server-reference-manifest.json +1 -1
  36. package/dist/preview/.next/static/chunks/490-d5745684930d49e0.js +1 -0
  37. package/dist/preview/.next/static/chunks/app/layout-d06046b8a368df3b.js +1 -0
  38. package/dist/preview/.next/static/chunks/app/page-ef1c23b954fbd0b5.js +1 -0
  39. package/dist/preview/.next/static/chunks/app/preview/[...slug]/{page-9906dc842681db05.js → page-ea8e1ae2b5a4a0ec.js} +1 -1
  40. package/dist/preview/.next/static/chunks/{main-app-d1b0aa870bcfb13e.js → main-app-9f2fb5ea26e2765b.js} +1 -1
  41. package/dist/preview/.next/static/css/e4822d5ba3082a95.css +3 -0
  42. package/dist/preview/.next/trace +22 -22
  43. package/dist/preview/.next/types/app/layout.ts +1 -1
  44. package/dist/preview/.next/types/app/preview/[...slug]/page.ts +1 -1
  45. package/package.json +3 -2
  46. package/src/app/preview/[...slug]/preview.tsx +25 -22
  47. package/src/components/icons/icon-bug.tsx +19 -0
  48. package/src/components/shell.tsx +3 -0
  49. package/src/components/sidebar/file-tree-directory-children.tsx +15 -12
  50. package/src/components/sidebar/image-checker.tsx +2 -1
  51. package/src/components/sidebar/sidebar.tsx +36 -2
  52. package/src/components/sidebar/spam-assassin.tsx +158 -0
  53. package/src/utils/__snapshots__/get-email-component.spec.ts.snap +1 -1
  54. package/tsconfig.json +1 -1
  55. package/vitest.config.ts +5 -3
  56. package/dist/preview/.next/server/chunks/590.js +0 -1
  57. package/dist/preview/.next/static/chunks/490-d26ba2019ccd4d2f.js +0 -1
  58. package/dist/preview/.next/static/chunks/app/layout-b13c19549e2d3e57.js +0 -1
  59. package/dist/preview/.next/static/chunks/app/page-8f366f3c14282f33.js +0 -1
  60. package/dist/preview/.next/static/css/b60917edfd15a496.css +0 -3
  61. package/tsconfig.test.json +0 -8
  62. /package/dist/preview/.next/static/{ll_lhpCErxdDFU8uF5Ujy → Pt6wqIrWnQxbiyqaKNFOx}/_buildManifest.js +0 -0
  63. /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/chore-1/packages/react-email/src/app/layout.tsx
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/chore-1/packages/react-email/src/app/preview/[...slug]/page.tsx
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.2",
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 pb-8 bg-gray-200"
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="mx-auto flex max-w-3xl gap-6 p-6">
198
- <Tooltip.Provider>
199
- <CodeContainer
200
- activeLang={activeLang}
201
- markups={[
202
- {
203
- language: 'jsx',
204
- content: renderedEmailMetadata.reactMarkup,
205
- },
206
- {
207
- language: 'markup',
208
- content: renderedEmailMetadata.markup,
209
- },
210
- {
211
- language: 'markdown',
212
- content: renderedEmailMetadata.plainText,
213
- },
214
- ]}
215
- setActiveLang={handleLangChange}
216
- />
217
- </Tooltip.Provider>
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
+ ));
@@ -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 pl-3 align-middle text-slate-11 text-sm transition-colors duration-100 ease-[cubic-bezier(.6,.12,.34,.96)]',
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
- <motion.div
100
- className="absolute top-1 left-[.625rem] h-6 w-px rounded-sm bg-cyan-11"
101
- layoutId="active-file"
102
- transition={{
103
- type: 'spring',
104
- bounce: 0.2,
105
- duration: 0.6,
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="absolute left-4 h-5 w-5"
114
+ className="h-5 w-5"
112
115
  height="20"
113
116
  width="20"
114
117
  />
115
- <span className="truncate pl-8">{emailFilename}</span>
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 links are valid and redirect to the correct pages.
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 = 'file-tree' | 'link-checker' | 'image-checker';
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><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, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;, &quot;Noto Color Emoji&quot;;padding-left:0.5rem;padding-right:0.5rem"><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>&#8202;&#8202;</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>&#8202;&#8202;&#8203;</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&#x27;s safety, please reply to this email to get in touch with us.</p></td></tr></tbody></table><!--/$--></body></html>"`;
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, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;, &quot;Noto Color Emoji&quot;;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>&#8202;&#8202;</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>&#8202;&#8202;&#8203;</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&#x27;s safety, please reply to this email to get in touch with us.</p></td></tr></tbody></table><!--/$--></body></html>"`;
package/tsconfig.json CHANGED
@@ -35,5 +35,5 @@
35
35
  "outDir": "dist"
36
36
  },
37
37
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
38
- "exclude": [".next", "dist", "node_modules", "**/*.spec.ts", "**/*.spec.tsx"]
38
+ "exclude": [".next", "dist", "node_modules"]
39
39
  }
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: tsconfig as TsconfigRaw,
9
+ tsconfigRaw: {
10
+ compilerOptions: {
11
+ jsx: 'react-jsx',
12
+ },
13
+ },
12
14
  },
13
15
  });