react-email 1.0.4 → 1.0.6
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/.babelrc +3 -0
- package/.eslintrc.js +4 -0
- package/base/.eslintrc.js +4 -0
- package/base/.next/build-manifest.json +35 -0
- package/base/.next/cache/webpack/client-development/0.pack +0 -0
- package/base/.next/cache/webpack/client-development/1.pack +0 -0
- package/base/.next/cache/webpack/client-development/index.pack +0 -0
- package/base/.next/cache/webpack/client-development/index.pack.old +0 -0
- package/base/.next/cache/webpack/server-development/0.pack +0 -0
- package/base/.next/cache/webpack/server-development/1.pack +0 -0
- package/base/.next/cache/webpack/server-development/index.pack +0 -0
- package/base/.next/cache/webpack/server-development/index.pack.old +0 -0
- package/base/.next/package.json +1 -0
- package/base/.next/react-loadable-manifest.json +1 -0
- package/base/.next/server/emails_notion-magic-link_tsx.js +25 -0
- package/base/.next/server/emails_stripe-welcome_tsx.js +25 -0
- package/base/.next/server/emails_vercel-invite-user_tsx.js +25 -0
- package/base/.next/server/middleware-build-manifest.js +1 -0
- package/base/.next/server/middleware-manifest.json +6 -0
- package/base/.next/server/middleware-react-loadable-manifest.js +1 -0
- package/base/.next/server/pages/[...path].js +386 -0
- package/base/.next/server/pages/_app.js +145 -0
- package/base/.next/server/pages/_document.js +211 -0
- package/base/.next/server/pages/_error.js +56 -0
- package/base/.next/server/pages/api/[...path].js +253 -0
- package/base/.next/server/pages-manifest.json +7 -0
- package/base/.next/server/webpack-api-runtime.js +168 -0
- package/base/.next/server/webpack-runtime.js +168 -0
- package/base/.next/static/chunks/amp.js +872 -0
- package/base/.next/static/chunks/main.js +1166 -0
- package/base/.next/static/chunks/pages/[...path].js +904 -0
- package/base/.next/static/chunks/pages/_app.js +467 -0
- package/base/.next/static/chunks/pages/_error.js +28 -0
- package/base/.next/static/chunks/polyfills.js +1 -0
- package/base/.next/static/chunks/react-refresh.js +62 -0
- package/base/.next/static/chunks/webpack.js +1242 -0
- package/base/.next/static/development/_buildManifest.js +1 -0
- package/base/.next/static/development/_ssgManifest.js +1 -0
- package/base/.next/static/webpack/94b31eaefc86ea7b.webpack.hot-update.json +1 -0
- package/base/.next/static/webpack/webpack.94b31eaefc86ea7b.hot-update.js +36 -0
- package/base/.next/trace +5 -0
- package/base/next-env.d.ts +5 -0
- package/base/next.config.js +11 -0
- package/base/package.json +53 -0
- package/base/postcss.config.js +20 -0
- package/base/public/static/fonts/Biotif-Bold.woff +0 -0
- package/base/public/static/fonts/Biotif-Bold.woff2 +0 -0
- package/base/public/static/fonts/Biotif-Book.woff +0 -0
- package/base/public/static/fonts/Biotif-Book.woff2 +0 -0
- package/base/public/static/fonts/Biotif-Regular.woff +0 -0
- package/base/public/static/fonts/Biotif-Regular.woff2 +0 -0
- package/base/public/static/fonts/Biotif-SemiBold.woff +0 -0
- package/base/public/static/fonts/Biotif-SemiBold.woff2 +0 -0
- package/base/public/static/fonts/FiraCode-Regular.woff +0 -0
- package/base/public/static/fonts/FiraCode-Regular.woff2 +0 -0
- package/base/public/static/images/favicon.ico +0 -0
- package/base/public/static/images/notion-logo.png +0 -0
- package/base/public/static/images/stripe-logo.png +0 -0
- package/base/public/static/images/vercel-arrow.png +0 -0
- package/base/public/static/images/vercel-logo.png +0 -0
- package/base/readme.md +19 -0
- package/base/src/components/code/code.tsx +152 -0
- package/base/src/components/code/index.ts +1 -0
- package/base/src/components/icon-button/icon-button.tsx +23 -0
- package/base/src/components/icon-button/index.ts +1 -0
- package/base/src/components/icons/icon-base.tsx +26 -0
- package/base/src/components/icons/icon-check.tsx +18 -0
- package/base/src/components/icons/icon-clipboard.tsx +39 -0
- package/base/src/components/icons/icon-download.tsx +18 -0
- package/base/src/components/icons/icon-loading.tsx +33 -0
- package/base/src/components/icons/index.ts +4 -0
- package/base/src/components/layout.tsx +53 -0
- package/base/src/components/tooltip/index.ts +1 -0
- package/base/src/components/tooltip/tooltip-arrow.tsx +1 -0
- package/base/src/components/tooltip/tooltip-content.tsx +26 -0
- package/base/src/components/tooltip/tooltip-provider.tsx +1 -0
- package/base/src/components/tooltip/tooltip-trigger.tsx +1 -0
- package/base/src/components/tooltip/tooltip.tsx +22 -0
- package/base/src/components/topbar/external.tsx +30 -0
- package/base/src/components/topbar/feedback.tsx +89 -0
- package/base/src/components/topbar/logo.tsx +27 -0
- package/base/src/components/topbar/send-test.tsx +95 -0
- package/base/src/components/topbar/toggle-view.tsx +66 -0
- package/base/src/helpers/bulb-icon.json +1 -0
- package/base/src/helpers/chat-icon.json +1 -0
- package/base/src/helpers/check-icon.json +1 -0
- package/base/src/helpers/code-icon.json +1 -0
- package/base/src/helpers/copy-icon.json +1 -0
- package/base/src/helpers/desktop-icon.json +1 -0
- package/base/src/helpers/download-icon.json +1 -0
- package/base/src/helpers/eye-icon.json +1 -0
- package/base/src/helpers/file-icon.json +1 -0
- package/base/src/helpers/folder-icon.json +1 -0
- package/base/src/helpers/inbox-icon.json +1 -0
- package/base/src/helpers/launch-icon.json +1 -0
- package/base/src/helpers/mail-icon.json +1 -0
- package/base/src/helpers/mobile-icon.json +1 -0
- package/base/src/helpers/note-icon.json +1 -0
- package/base/src/helpers/reply-icon.json +1 -0
- package/base/src/pages/[...path].tsx +40 -0
- package/base/src/pages/_app.tsx +11 -0
- package/base/src/pages/_document.tsx +37 -0
- package/base/src/pages/api/[...path].js +20 -0
- package/base/src/styles/globals.css +48 -0
- package/base/src/utils/copy-text-to-clipboard.ts +7 -0
- package/base/tsconfig.json +5 -0
- package/license.md +7 -0
- package/package.json +10 -38
- package/src/index.js +30 -0
- package/tsconfig.json +8 -0
- package/build/index.d.ts +0 -2
- package/build/index.js +0 -39
- package/readme.md +0 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { IconBase, IconElement, IconProps } from './icon-base';
|
|
3
|
+
|
|
4
|
+
export const IconDownload = React.forwardRef<IconElement, Readonly<IconProps>>(
|
|
5
|
+
({ ...props }, forwardedRef) => (
|
|
6
|
+
<IconBase ref={forwardedRef} {...props}>
|
|
7
|
+
<path
|
|
8
|
+
d="M4.75 14.75v1.5a3 3 0 0 0 3 3h8.5a3 3 0 0 0 3-3v-1.5M12 14.25v-9.5M8.75 10.75l3.25 3.5 3.25-3.5"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
strokeWidth={1.5}
|
|
11
|
+
strokeLinecap="round"
|
|
12
|
+
strokeLinejoin="round"
|
|
13
|
+
/>
|
|
14
|
+
</IconBase>
|
|
15
|
+
),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
IconDownload.displayName = 'IconDownload';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import classnames from 'classnames';
|
|
3
|
+
import { IconBase, IconElement, IconProps } from './icon-base';
|
|
4
|
+
|
|
5
|
+
export const IconLoading = React.forwardRef<IconElement, Readonly<IconProps>>(
|
|
6
|
+
({ className, ...props }, forwardedRef) => (
|
|
7
|
+
<IconBase
|
|
8
|
+
ref={forwardedRef}
|
|
9
|
+
className={classnames('animate-spin', className)}
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<path
|
|
13
|
+
className="opacity-[15%]"
|
|
14
|
+
d="M5 12C5 8.13401 8.13401 5 12 5C15.866 5 19 8.13401 19 12C19 15.866 15.866 19 12 19C8.13401 19 5 15.866 5 12Z"
|
|
15
|
+
stroke="currentColor"
|
|
16
|
+
strokeWidth="2"
|
|
17
|
+
strokeLinecap="round"
|
|
18
|
+
strokeLinejoin="round"
|
|
19
|
+
/>
|
|
20
|
+
<circle
|
|
21
|
+
cx="12"
|
|
22
|
+
cy="12"
|
|
23
|
+
r="7"
|
|
24
|
+
stroke="currentColor"
|
|
25
|
+
strokeWidth="2"
|
|
26
|
+
strokeDasharray="150,200"
|
|
27
|
+
strokeDashoffset="-16"
|
|
28
|
+
/>
|
|
29
|
+
</IconBase>
|
|
30
|
+
),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
IconLoading.displayName = 'IconLoading';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import Logo from '../components/topbar/logo';
|
|
3
|
+
import External from '../components/topbar/external';
|
|
4
|
+
import Feedback from '../components/topbar/feedback';
|
|
5
|
+
import SendTest from '../components/topbar/send-test';
|
|
6
|
+
import ToggleView from '../components/topbar/toggle-view';
|
|
7
|
+
import { Code } from '../components/code';
|
|
8
|
+
|
|
9
|
+
export default function Layout({
|
|
10
|
+
markup,
|
|
11
|
+
path,
|
|
12
|
+
}: {
|
|
13
|
+
markup: string;
|
|
14
|
+
path: string;
|
|
15
|
+
}) {
|
|
16
|
+
const [isPreview, setIsPreview] = React.useState(true);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<div className="w-full bg-gray-1 text-gray-12">
|
|
21
|
+
<nav className="max-w-6xl mx-auto flex items-center justify-between m-2 p-2">
|
|
22
|
+
<div className="w-60">
|
|
23
|
+
<Logo />
|
|
24
|
+
</div>
|
|
25
|
+
<div className="w-60">
|
|
26
|
+
<ToggleView isPreview={isPreview} setIsPreview={setIsPreview} />
|
|
27
|
+
</div>
|
|
28
|
+
<div className="w-60 flex gap-2 justify-end">
|
|
29
|
+
<SendTest markup={markup} />
|
|
30
|
+
<div className="flex items-center border-l ml-2 pl-4 border-gray-8">
|
|
31
|
+
<div className="flex gap-2 items-center justify-center">
|
|
32
|
+
<Feedback />
|
|
33
|
+
<External path={path} />
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</nav>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
{isPreview ? (
|
|
41
|
+
<iframe
|
|
42
|
+
srcDoc={markup}
|
|
43
|
+
className="w-full bg-white"
|
|
44
|
+
style={{ height: 'calc(100vh - 60px)' }}
|
|
45
|
+
/>
|
|
46
|
+
) : (
|
|
47
|
+
<div className="max-w-6xl mx-auto">
|
|
48
|
+
<Code>{markup}</Code>
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './tooltip';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TooltipArrow } from '@radix-ui/react-tooltip';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Arrow, Content, Portal } from '@radix-ui/react-tooltip';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
type ContentElement = React.ElementRef<typeof Content>;
|
|
5
|
+
type ContentProps = React.ComponentPropsWithoutRef<typeof Content>;
|
|
6
|
+
|
|
7
|
+
export interface TooltipProps extends ContentProps {}
|
|
8
|
+
|
|
9
|
+
export const TooltipContent = React.forwardRef<
|
|
10
|
+
ContentElement,
|
|
11
|
+
Readonly<TooltipProps>
|
|
12
|
+
>(({ className, sideOffset = 6, children, ...props }, forwardedRef) => (
|
|
13
|
+
<Portal>
|
|
14
|
+
<Content
|
|
15
|
+
{...props}
|
|
16
|
+
ref={forwardedRef}
|
|
17
|
+
className="bg-gray-2 border border-gray-7 z-20 px-2 py-1 rounded-md text-sm radix-side-top:animate-slide-down-fade radix-side-right:animate-slide-left-fade radix-side-bottom:animate-slide-up-fade radix-side-left:animate-slide-right-fade"
|
|
18
|
+
sideOffset={sideOffset}
|
|
19
|
+
>
|
|
20
|
+
{children}
|
|
21
|
+
<Arrow fill="#1c1c1f" />
|
|
22
|
+
</Content>
|
|
23
|
+
</Portal>
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
TooltipContent.displayName = 'TooltipContent';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TooltipProvider } from '@radix-ui/react-tooltip';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TooltipTrigger } from '@radix-ui/react-tooltip';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Root } from '@radix-ui/react-tooltip';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { TooltipArrow } from './tooltip-arrow';
|
|
4
|
+
import { TooltipContent } from './tooltip-content';
|
|
5
|
+
import { TooltipProvider } from './tooltip-provider';
|
|
6
|
+
import { TooltipTrigger } from './tooltip-trigger';
|
|
7
|
+
|
|
8
|
+
type RootProps = React.ComponentPropsWithoutRef<typeof Root>;
|
|
9
|
+
|
|
10
|
+
export interface TooltipProps extends RootProps {}
|
|
11
|
+
|
|
12
|
+
export const TooltipRoot: React.FC<Readonly<TooltipProps>> = ({
|
|
13
|
+
children,
|
|
14
|
+
...props
|
|
15
|
+
}) => <Root {...props}>{children}</Root>;
|
|
16
|
+
|
|
17
|
+
export const Tooltip = Object.assign(TooltipRoot, {
|
|
18
|
+
Arrow: TooltipArrow,
|
|
19
|
+
Provider: TooltipProvider,
|
|
20
|
+
Content: TooltipContent,
|
|
21
|
+
Trigger: TooltipTrigger,
|
|
22
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import launchIcon from '../../helpers/launch-icon.json';
|
|
3
|
+
import Lottie, { LottieRefCurrentProps } from 'lottie-react';
|
|
4
|
+
|
|
5
|
+
export default function External({ path }: { path: string }) {
|
|
6
|
+
const launchIconRef = React.useRef<LottieRefCurrentProps>(null);
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<a
|
|
10
|
+
href={`/api/${path}`}
|
|
11
|
+
rel="noreferrer"
|
|
12
|
+
target="_blank"
|
|
13
|
+
className="text-current transition duration-300 ease-in-out hover:opacity-60"
|
|
14
|
+
onMouseEnter={() => {
|
|
15
|
+
launchIconRef.current?.play();
|
|
16
|
+
}}
|
|
17
|
+
onMouseLeave={() => {
|
|
18
|
+
launchIconRef.current?.stop();
|
|
19
|
+
}}
|
|
20
|
+
>
|
|
21
|
+
<Lottie
|
|
22
|
+
lottieRef={launchIconRef}
|
|
23
|
+
className="mr-1 w-5 h-5"
|
|
24
|
+
animationData={launchIcon}
|
|
25
|
+
loop={false}
|
|
26
|
+
autoplay={false}
|
|
27
|
+
/>
|
|
28
|
+
</a>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as Popover from '@radix-ui/react-popover';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import inboxIcon from '../../helpers/inbox-icon.json';
|
|
4
|
+
import Lottie, { LottieRefCurrentProps } from 'lottie-react';
|
|
5
|
+
|
|
6
|
+
export default function Feedback() {
|
|
7
|
+
const [message, setMessage] = React.useState('');
|
|
8
|
+
const [isSending, setIsSending] = React.useState(false);
|
|
9
|
+
const inboxIconRef = React.useRef<LottieRefCurrentProps>(null);
|
|
10
|
+
|
|
11
|
+
const onFormSubmit = async (e: React.FormEvent) => {
|
|
12
|
+
try {
|
|
13
|
+
e.preventDefault();
|
|
14
|
+
setIsSending(true);
|
|
15
|
+
|
|
16
|
+
await fetch('https://react.email/api/send/feedback', {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: { 'Content-Type': 'application/json' },
|
|
19
|
+
body: JSON.stringify({
|
|
20
|
+
message,
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
} catch (e) {
|
|
24
|
+
alert('Something went wrong. Please try again.');
|
|
25
|
+
} finally {
|
|
26
|
+
setIsSending(false);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Popover.Root>
|
|
32
|
+
<Popover.Trigger asChild>
|
|
33
|
+
<button
|
|
34
|
+
className="text-current transition duration-300 ease-in-out hover:opacity-60"
|
|
35
|
+
onMouseEnter={() => {
|
|
36
|
+
inboxIconRef.current?.play();
|
|
37
|
+
}}
|
|
38
|
+
onMouseLeave={() => {
|
|
39
|
+
inboxIconRef.current?.stop();
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
<Lottie
|
|
43
|
+
lottieRef={inboxIconRef}
|
|
44
|
+
className="w-5 h-5"
|
|
45
|
+
animationData={inboxIcon}
|
|
46
|
+
loop={false}
|
|
47
|
+
autoplay={false}
|
|
48
|
+
/>
|
|
49
|
+
</button>
|
|
50
|
+
</Popover.Trigger>
|
|
51
|
+
<Popover.Anchor />
|
|
52
|
+
<Popover.Portal>
|
|
53
|
+
<Popover.Content
|
|
54
|
+
align="end"
|
|
55
|
+
className="w-80 -mt-6 p-3 bg-gray-1 border border-gray-8 rounded-lg"
|
|
56
|
+
>
|
|
57
|
+
<Popover.Close
|
|
58
|
+
aria-label="Close"
|
|
59
|
+
className="absolute right-2 flex items-center justify-center w-6 h-6 text-xs text-gray-11 hover:text-gray-12 transition duration-300 ease-in-out rounded-full"
|
|
60
|
+
>
|
|
61
|
+
✕
|
|
62
|
+
</Popover.Close>
|
|
63
|
+
<form onSubmit={onFormSubmit}>
|
|
64
|
+
<label className="text-gray-10 text-xs uppercase">Feedback</label>
|
|
65
|
+
<textarea
|
|
66
|
+
autoFocus={true}
|
|
67
|
+
className="h-28 w-full resize-none rounded-lg p-3 mt-1 text-sm outline-none border border-gray-8 bg-gray-3 focus:ring-1 focus:ring-gray-10 transition duration-300 ease-in-out"
|
|
68
|
+
id="feedback"
|
|
69
|
+
name="feedback"
|
|
70
|
+
onChange={(e) => setMessage(e.target.value)}
|
|
71
|
+
value={message}
|
|
72
|
+
placeholder="What if..."
|
|
73
|
+
required
|
|
74
|
+
/>
|
|
75
|
+
<div className="flex items-center justify-end">
|
|
76
|
+
<button
|
|
77
|
+
type="submit"
|
|
78
|
+
disabled={message.length === 0 || isSending}
|
|
79
|
+
className="box-border outline-none self-center relative w-20 h-8 inline-flex items-center justify-center rounded-lg text-center font-semibold transition duration-300 ease-in-out bg-gray-12 text-gray-1 text-sm px-4 hover:bg-cyan-1 focus:ring-1 focus:ring-gray-12 disabled:bg-gray-11"
|
|
80
|
+
>
|
|
81
|
+
Send
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
</form>
|
|
85
|
+
</Popover.Content>
|
|
86
|
+
</Popover.Portal>
|
|
87
|
+
</Popover.Root>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export default function Logo() {
|
|
4
|
+
return (
|
|
5
|
+
<a
|
|
6
|
+
href="https://react.email"
|
|
7
|
+
target="_blank"
|
|
8
|
+
className="transition duration-300 ease-in-out hover:opacity-60"
|
|
9
|
+
rel="noreferrer"
|
|
10
|
+
>
|
|
11
|
+
<svg
|
|
12
|
+
width="16"
|
|
13
|
+
height="16"
|
|
14
|
+
viewBox="0 0 107 106"
|
|
15
|
+
fill="none"
|
|
16
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
17
|
+
>
|
|
18
|
+
<path
|
|
19
|
+
fillRule="evenodd"
|
|
20
|
+
clipRule="evenodd"
|
|
21
|
+
d="M101.25 101.083C105.659 96.6745 106.49 90.7112 106.003 85.5605C105.511 80.344 103.595 74.6796 100.875 69.0295C98.4229 63.9377 95.1545 58.522 91.1988 53C95.1545 47.478 98.4229 42.0623 100.875 36.9705C103.595 31.3204 105.511 25.656 106.003 20.4395C106.49 15.2888 105.659 9.32548 101.25 4.91674C96.841 0.507999 90.8777 -0.323118 85.727 0.163082C80.5106 0.65549 74.8462 2.57153 69.196 5.29199C64.1042 7.74358 58.6885 11.012 53.1665 14.9677C47.6445 11.012 42.2288 7.74358 37.137 5.29199C31.4869 2.57153 25.8225 0.655494 20.606 0.163081C15.4553 -0.323121 9.492 0.507999 5.08326 4.91674C0.674519 9.32548 -0.156601 15.2888 0.329602 20.4395C0.822012 25.656 2.73805 31.3204 5.45851 36.9705C7.9101 42.0623 11.1786 47.478 15.1342 53C11.1786 58.522 7.9101 63.9377 5.45851 69.0295C2.73805 74.6796 0.822012 80.344 0.329602 85.5605C-0.156601 90.7112 0.674519 96.6745 5.08326 101.083C9.492 105.492 15.4553 106.323 20.606 105.837C25.8225 105.345 31.4869 103.428 37.137 100.708C42.2288 98.2564 47.6445 94.988 53.1665 91.0323C58.6885 94.988 64.1042 98.2564 69.196 100.708C74.8462 103.428 80.5106 105.345 85.727 105.837C90.8777 106.323 96.841 105.492 101.25 101.083ZM53.1665 81.0388C58.0927 77.1877 63.1029 72.7621 68.0158 67.8492C72.9287 62.9363 77.3543 57.9262 81.2053 53C77.3543 48.0738 72.9287 43.0637 68.0158 38.1508C63.1029 33.2379 58.0927 28.8123 53.1665 24.9612C48.2403 28.8123 43.2302 33.2379 38.3173 38.1508C33.4044 43.0637 28.9788 48.0738 25.1277 53C28.9788 57.9262 33.4044 62.9363 38.3173 67.8492C43.2302 72.7621 48.2403 77.1877 53.1665 81.0388ZM59.8486 85.949C64.4852 82.2163 69.135 78.0437 73.6726 73.5061C78.2102 68.9685 82.3828 64.3187 86.1155 59.6821C97.0729 75.6209 101.289 89.7305 95.5929 95.4264C89.897 101.122 75.7874 96.9064 59.8486 85.949ZM20.2175 59.6821C23.9503 64.3187 28.1228 68.9685 32.6604 73.5061C37.198 78.0437 41.8478 82.2163 46.4844 85.949C30.5457 96.9064 16.436 101.122 10.7401 95.4264C5.04422 89.7305 9.26014 75.6209 20.2175 59.6821ZM20.2175 46.3179C23.9503 41.6813 28.1228 37.0315 32.6604 32.4939C37.198 27.9563 41.8478 23.7837 46.4844 20.051C30.5457 9.09362 16.436 4.8777 10.7401 10.5736C5.04422 16.2695 9.26014 30.3792 20.2175 46.3179ZM59.8486 20.051C64.4852 23.7837 69.135 27.9563 73.6726 32.4939C78.2102 37.0315 82.3828 41.6813 86.1155 46.3179C97.0729 30.3791 101.289 16.2695 95.5929 10.5736C89.897 4.87769 75.7874 9.09362 59.8486 20.051Z"
|
|
22
|
+
fill="#F2F2F1"
|
|
23
|
+
/>
|
|
24
|
+
</svg>
|
|
25
|
+
</a>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import * as Popover from '@radix-ui/react-popover';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
export default function TestEmail({ markup }: { markup: string }) {
|
|
5
|
+
const [to, setTo] = React.useState('');
|
|
6
|
+
const [subject, setSubject] = React.useState(
|
|
7
|
+
'bukinoshita invited you to My Project team on Vercel',
|
|
8
|
+
);
|
|
9
|
+
const [isSending, setIsSending] = React.useState(false);
|
|
10
|
+
|
|
11
|
+
const onFormSubmit = async (e: React.FormEvent) => {
|
|
12
|
+
try {
|
|
13
|
+
e.preventDefault();
|
|
14
|
+
setIsSending(true);
|
|
15
|
+
|
|
16
|
+
await fetch('https://react.email/api/send/test', {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: { 'Content-Type': 'application/json' },
|
|
19
|
+
body: JSON.stringify({
|
|
20
|
+
to,
|
|
21
|
+
subject,
|
|
22
|
+
html: markup,
|
|
23
|
+
}),
|
|
24
|
+
});
|
|
25
|
+
} catch (e) {
|
|
26
|
+
alert('Something went wrong. Please try again.');
|
|
27
|
+
} finally {
|
|
28
|
+
setIsSending(false);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Popover.Root>
|
|
34
|
+
<Popover.Trigger asChild>
|
|
35
|
+
<button className="box-border outline-none self-center relative w-20 h-5 inline-flex items-center justify-center rounded-lg text-center transition duration-300 ease-in-out border border-gray-8 text-gray-12 text-sm px-4 py-4 hover:border-gray-12">
|
|
36
|
+
Send
|
|
37
|
+
</button>
|
|
38
|
+
</Popover.Trigger>
|
|
39
|
+
<Popover.Anchor />
|
|
40
|
+
<Popover.Portal>
|
|
41
|
+
<Popover.Content
|
|
42
|
+
align="end"
|
|
43
|
+
className="w-80 -mt-10 p-3 bg-gray-1 border border-gray-8 rounded-lg"
|
|
44
|
+
>
|
|
45
|
+
<Popover.Close
|
|
46
|
+
aria-label="Close"
|
|
47
|
+
className="absolute right-2 flex items-center justify-center w-6 h-6 text-xs text-gray-11 hover:text-gray-12 transition duration-300 ease-in-out rounded-full"
|
|
48
|
+
>
|
|
49
|
+
✕
|
|
50
|
+
</Popover.Close>
|
|
51
|
+
<form onSubmit={onFormSubmit}>
|
|
52
|
+
<label htmlFor="to" className="text-gray-10 text-xs uppercase">
|
|
53
|
+
Recipient
|
|
54
|
+
</label>
|
|
55
|
+
<input
|
|
56
|
+
autoFocus={true}
|
|
57
|
+
className="appearance-none rounded-lg px-2 py-1 mb-3 outline-none w-full bg-gray-3 border placeholder-gray-11 border-gray-6 text-sm text-gray-12 focus:ring-1 focus:ring-gray-10 transition duration-300 ease-in-out"
|
|
58
|
+
onChange={(e) => setTo(e.target.value)}
|
|
59
|
+
defaultValue={to}
|
|
60
|
+
placeholder="you@example.com"
|
|
61
|
+
type="email"
|
|
62
|
+
id="to"
|
|
63
|
+
required
|
|
64
|
+
/>
|
|
65
|
+
<label htmlFor="subject" className="text-gray-10 text-xs uppercase">
|
|
66
|
+
Subject
|
|
67
|
+
</label>
|
|
68
|
+
<input
|
|
69
|
+
className="appearance-none rounded-lg px-2 py-1 mb-3 outline-none w-full bg-gray-3 border placeholder-gray-11 border-gray-6 text-sm text-gray-12 focus:ring-1 focus:ring-gray-10 transition duration-300 ease-in-out"
|
|
70
|
+
onChange={(e) => setSubject(e.target.value)}
|
|
71
|
+
defaultValue={subject}
|
|
72
|
+
placeholder="My Email"
|
|
73
|
+
type="text"
|
|
74
|
+
id="subject"
|
|
75
|
+
required
|
|
76
|
+
/>
|
|
77
|
+
<input
|
|
78
|
+
type="checkbox"
|
|
79
|
+
className="appearance-none checked:bg-blue-500"
|
|
80
|
+
/>
|
|
81
|
+
<div className="flex items-center justify-end">
|
|
82
|
+
<button
|
|
83
|
+
type="submit"
|
|
84
|
+
disabled={subject.length === 0 || to.length === 0 || isSending}
|
|
85
|
+
className="box-border outline-none self-center relative w-20 h-8 inline-flex items-center justify-center rounded-lg text-center font-semibold transition duration-300 ease-in-out bg-gray-12 text-gray-1 text-sm px-4 hover:bg-cyan-1 focus:bg-cyan-1 disabled:bg-gray-11"
|
|
86
|
+
>
|
|
87
|
+
Send
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
</form>
|
|
91
|
+
</Popover.Content>
|
|
92
|
+
</Popover.Portal>
|
|
93
|
+
</Popover.Root>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import codeIcon from '../../helpers/code-icon.json';
|
|
3
|
+
import eyeIcon from '../../helpers/eye-icon.json';
|
|
4
|
+
import Lottie, { LottieRefCurrentProps } from 'lottie-react';
|
|
5
|
+
|
|
6
|
+
interface ToggleViewProps {
|
|
7
|
+
isPreview?: boolean;
|
|
8
|
+
setIsPreview: (status: boolean) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function ToggleView({
|
|
12
|
+
isPreview,
|
|
13
|
+
setIsPreview,
|
|
14
|
+
}: ToggleViewProps) {
|
|
15
|
+
const codeIconRef = React.useRef<LottieRefCurrentProps>(null);
|
|
16
|
+
const eyeIconRef = React.useRef<LottieRefCurrentProps>(null);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex items-center justify-center">
|
|
20
|
+
<button
|
|
21
|
+
className={`text-current transition duration-300 ease-in-out px-2 border border-gray-8 rounded-l-lg ${
|
|
22
|
+
isPreview ? 'bg-gray-8' : 'bg-transparent'
|
|
23
|
+
}`}
|
|
24
|
+
onClick={() => {
|
|
25
|
+
setIsPreview(true);
|
|
26
|
+
}}
|
|
27
|
+
onMouseEnter={() => {
|
|
28
|
+
eyeIconRef.current?.play();
|
|
29
|
+
}}
|
|
30
|
+
onMouseLeave={() => {
|
|
31
|
+
eyeIconRef.current?.stop();
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
<Lottie
|
|
35
|
+
lottieRef={eyeIconRef}
|
|
36
|
+
className="w-5 h-8"
|
|
37
|
+
animationData={eyeIcon}
|
|
38
|
+
loop={false}
|
|
39
|
+
autoplay={false}
|
|
40
|
+
/>
|
|
41
|
+
</button>
|
|
42
|
+
<button
|
|
43
|
+
className={`text-current transition duration-300 ease-in-out px-2 border border-gray-8 rounded-r-lg ${
|
|
44
|
+
!isPreview ? 'bg-gray-8' : 'bg-transparent'
|
|
45
|
+
}`}
|
|
46
|
+
onClick={() => {
|
|
47
|
+
setIsPreview(false);
|
|
48
|
+
}}
|
|
49
|
+
onMouseEnter={() => {
|
|
50
|
+
codeIconRef.current?.play();
|
|
51
|
+
}}
|
|
52
|
+
onMouseLeave={() => {
|
|
53
|
+
codeIconRef.current?.stop();
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
<Lottie
|
|
57
|
+
lottieRef={codeIconRef}
|
|
58
|
+
className="w-5 h-8"
|
|
59
|
+
animationData={codeIcon}
|
|
60
|
+
loop={false}
|
|
61
|
+
autoplay={false}
|
|
62
|
+
/>
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|