mirror of
https://github.com/fastapi/full-stack-fastapi-template.git
synced 2026-11-03 12:29:42 +00:00
💄 Refactor email templates and components for improved styling (#2451)
This commit is contained in:
@@ -25,8 +25,8 @@ export default function NewAccount({
|
||||
project_name={project_name}
|
||||
>
|
||||
<Heading>Welcome to {project_name}!</Heading>
|
||||
<Text className="text-[15px] leading-7 text-body">Hi,</Text>
|
||||
<Text className="text-[15px] leading-7 text-body">
|
||||
<Text style={bodyTextStyle}>Hi,</Text>
|
||||
<Text style={bodyTextStyle}>
|
||||
Your account has been successfully created and is ready to use. These
|
||||
are your credentials:
|
||||
</Text>
|
||||
@@ -34,18 +34,32 @@ export default function NewAccount({
|
||||
<Detail label="Username" value={username} />
|
||||
<Detail label="Password" value={password} />
|
||||
</Callout>
|
||||
<Text className="text-[15px] leading-7 text-body">
|
||||
<Text style={bodyTextStyle}>
|
||||
Get started by signing in to your dashboard:
|
||||
</Text>
|
||||
<LinkButton href={link}>Go to Dashboard</LinkButton>
|
||||
<Text className="text-sm leading-6 text-muted">
|
||||
<Text style={supportingTextStyle}>
|
||||
Or copy and paste this link into your browser:
|
||||
<br />
|
||||
<Link href={link}>{link}</Link>
|
||||
</Text>
|
||||
<Text className="text-sm leading-6 text-muted">
|
||||
<Text style={supportingTextStyle}>
|
||||
For security reasons, change your password after your first sign in.
|
||||
</Text>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
const bodyTextStyle = {
|
||||
color: "#334155",
|
||||
fontSize: "15px",
|
||||
lineHeight: "26px",
|
||||
margin: "0 0 18px",
|
||||
}
|
||||
|
||||
const supportingTextStyle = {
|
||||
color: "#64748b",
|
||||
fontSize: "14px",
|
||||
lineHeight: "23px",
|
||||
margin: "0 0 16px",
|
||||
}
|
||||
|
||||
@@ -24,24 +24,38 @@ export default function ResetPassword({
|
||||
project_name={project_name}
|
||||
>
|
||||
<Heading>Reset your password</Heading>
|
||||
<Text className="text-[15px] leading-7 text-body">Hi {username},</Text>
|
||||
<Text className="text-[15px] leading-7 text-body">
|
||||
<Text style={bodyTextStyle}>Hi {username},</Text>
|
||||
<Text style={bodyTextStyle}>
|
||||
We've received a request to reset the password for your {project_name}{" "}
|
||||
account. Choose a new one by clicking the button below:
|
||||
</Text>
|
||||
<LinkButton href={link}>Reset password</LinkButton>
|
||||
<Text className="text-sm leading-6 text-muted">
|
||||
<Text style={supportingTextStyle}>
|
||||
Or copy and paste this link into your browser:
|
||||
<br />
|
||||
<Link href={link}>{link}</Link>
|
||||
</Text>
|
||||
<Text className="text-sm leading-6 text-muted">
|
||||
<Text style={supportingTextStyle}>
|
||||
This link will expire in {valid_hours} hours.
|
||||
</Text>
|
||||
<Text className="text-sm leading-6 text-muted">
|
||||
<Text style={supportingTextStyle}>
|
||||
If you didn't request a password recovery, you can safely ignore this
|
||||
email.
|
||||
</Text>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
const bodyTextStyle = {
|
||||
color: "#334155",
|
||||
fontSize: "15px",
|
||||
lineHeight: "26px",
|
||||
margin: "0 0 18px",
|
||||
}
|
||||
|
||||
const supportingTextStyle = {
|
||||
color: "#64748b",
|
||||
fontSize: "14px",
|
||||
lineHeight: "23px",
|
||||
margin: "0 0 16px",
|
||||
}
|
||||
|
||||
@@ -19,17 +19,31 @@ export default function TestEmail({
|
||||
project_name={project_name}
|
||||
>
|
||||
<Heading>Test email</Heading>
|
||||
<Text className="text-[15px] leading-7 text-body">Hi,</Text>
|
||||
<Text className="text-[15px] leading-7 text-body">
|
||||
<Text style={bodyTextStyle}>Hi,</Text>
|
||||
<Text style={bodyTextStyle}>
|
||||
This is a test email from {project_name}. If you're reading it, email
|
||||
delivery is configured correctly.
|
||||
</Text>
|
||||
<Callout>
|
||||
<Detail label="Sent to" value={email} />
|
||||
</Callout>
|
||||
<Text className="text-sm leading-6 text-muted">
|
||||
<Text style={supportingTextStyle}>
|
||||
If you weren't expecting this email, you can safely ignore it.
|
||||
</Text>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
const bodyTextStyle = {
|
||||
color: "#334155",
|
||||
fontSize: "15px",
|
||||
lineHeight: "26px",
|
||||
margin: "0 0 18px",
|
||||
}
|
||||
|
||||
const supportingTextStyle = {
|
||||
color: "#64748b",
|
||||
fontSize: "14px",
|
||||
lineHeight: "23px",
|
||||
margin: "0 0 16px",
|
||||
}
|
||||
|
||||
@@ -3,10 +3,21 @@ import { Button } from "@react-email/components"
|
||||
type ButtonProps = React.PropsWithChildren & { href: string }
|
||||
|
||||
export const LinkButton = ({ children, href }: ButtonProps) => (
|
||||
<Button
|
||||
href={href}
|
||||
className="bg-brand text-white rounded-md font-semibold text-sm mt-2 mb-7 inline-block text-center no-underline px-7 py-3.5"
|
||||
>
|
||||
<Button href={href} style={buttonStyle}>
|
||||
{children}
|
||||
</Button>
|
||||
)
|
||||
|
||||
const buttonStyle = {
|
||||
backgroundColor: "#00796b",
|
||||
borderRadius: "6px",
|
||||
color: "#ffffff",
|
||||
display: "inline-block",
|
||||
fontSize: "14px",
|
||||
fontWeight: "700",
|
||||
lineHeight: "20px",
|
||||
margin: "8px 0 28px",
|
||||
padding: "13px 28px",
|
||||
textAlign: "center" as const,
|
||||
textDecoration: "none",
|
||||
}
|
||||
|
||||
@@ -3,20 +3,44 @@ import { Section, Text } from "@react-email/components"
|
||||
type CalloutProps = React.PropsWithChildren
|
||||
|
||||
export const Callout = ({ children }: CalloutProps) => (
|
||||
<Section className="border-0 border-l-2 border-solid border-brand pl-5 my-7">
|
||||
{children}
|
||||
</Section>
|
||||
<Section style={calloutStyle}>{children}</Section>
|
||||
)
|
||||
|
||||
type DetailProps = { label: string; value: string }
|
||||
|
||||
export const Detail = ({ label, value }: DetailProps) => (
|
||||
<Text className="m-0 my-3">
|
||||
<span className="block text-[11px] uppercase tracking-[0.08em] text-muted">
|
||||
{label}
|
||||
</span>
|
||||
<span className="block text-sm font-semibold break-all font-mono mt-1">
|
||||
{value}
|
||||
</span>
|
||||
<Text style={detailStyle}>
|
||||
<span style={labelStyle}>{label}</span>
|
||||
<br />
|
||||
<span style={valueStyle}>{value}</span>
|
||||
</Text>
|
||||
)
|
||||
|
||||
const calloutStyle = {
|
||||
backgroundColor: "#f2f8f7",
|
||||
borderLeft: "3px solid #00897b",
|
||||
margin: "24px 0 28px",
|
||||
padding: "6px 18px",
|
||||
}
|
||||
|
||||
const detailStyle = {
|
||||
margin: "12px 0",
|
||||
}
|
||||
|
||||
const labelStyle = {
|
||||
color: "#64748b",
|
||||
fontSize: "11px",
|
||||
fontWeight: "700",
|
||||
letterSpacing: "1px",
|
||||
lineHeight: "16px",
|
||||
textTransform: "uppercase" as const,
|
||||
}
|
||||
|
||||
const valueStyle = {
|
||||
color: "#1e293b",
|
||||
fontFamily: 'Consolas, "Courier New", monospace',
|
||||
fontSize: "14px",
|
||||
fontWeight: "700",
|
||||
lineHeight: "22px",
|
||||
wordBreak: "break-all" as const,
|
||||
}
|
||||
|
||||
@@ -3,10 +3,16 @@ import { Heading as BaseHeading } from "@react-email/components"
|
||||
type HeadingProps = React.PropsWithChildren
|
||||
|
||||
export const Heading = ({ children }: HeadingProps) => (
|
||||
<BaseHeading
|
||||
as="h1"
|
||||
className="text-2xl leading-8 font-semibold tracking-[-0.02em] text-body m-0 mb-5"
|
||||
>
|
||||
<BaseHeading as="h1" style={headingStyle}>
|
||||
{children}
|
||||
</BaseHeading>
|
||||
)
|
||||
|
||||
const headingStyle = {
|
||||
color: "#17212b",
|
||||
fontSize: "26px",
|
||||
fontWeight: "700",
|
||||
letterSpacing: "-0.3px",
|
||||
lineHeight: "34px",
|
||||
margin: "0 0 20px",
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import {
|
||||
Body,
|
||||
Column,
|
||||
Container,
|
||||
Head,
|
||||
Hr,
|
||||
Html,
|
||||
Preview,
|
||||
Row,
|
||||
Section,
|
||||
Tailwind,
|
||||
Text,
|
||||
} from "@react-email/components"
|
||||
|
||||
@@ -22,64 +24,88 @@ export const Layout = ({
|
||||
children,
|
||||
}: LayoutProps) => {
|
||||
return (
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
surface: "#ffffff",
|
||||
body: "#1e293b",
|
||||
muted: "#64748b",
|
||||
rule: "#e2e8f0",
|
||||
brand: {
|
||||
DEFAULT: "#009688",
|
||||
dark: "#00695c",
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [
|
||||
"-apple-system",
|
||||
"BlinkMacSystemFont",
|
||||
'"Segoe UI"',
|
||||
"Roboto",
|
||||
"Oxygen",
|
||||
"Ubuntu",
|
||||
"Cantarell",
|
||||
'"Fira Sans"',
|
||||
'"Droid Sans"',
|
||||
'"Helvetica Neue"',
|
||||
"sans-serif",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Html lang="en" dir="ltr">
|
||||
<Head>
|
||||
<title>{title}</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0"
|
||||
/>
|
||||
</Head>
|
||||
{preview ? <Preview>{preview}</Preview> : null}
|
||||
<Body className="bg-surface font-sans text-body max-w-[560px] w-full mx-auto my-0 py-12 px-6">
|
||||
<Section>
|
||||
<Hr className="border-0 border-t-2 border-solid border-brand w-8 m-0" />
|
||||
<Text className="text-body text-[13px] font-semibold uppercase tracking-[0.1em] mt-4 mb-9">
|
||||
{project_name}
|
||||
</Text>
|
||||
</Section>
|
||||
<Section>{children}</Section>
|
||||
<Hr className="border-0 border-t border-solid border-rule mt-10 mb-5" />
|
||||
<Section>
|
||||
<Text className="text-xs leading-5 text-muted m-0">
|
||||
© {new Date().getFullYear()} {project_name}. All rights reserved.
|
||||
</Text>
|
||||
</Section>
|
||||
</Body>
|
||||
</Html>
|
||||
</Tailwind>
|
||||
<Html lang="en" dir="ltr">
|
||||
<Head>
|
||||
<title>{title}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</Head>
|
||||
{preview ? <Preview>{preview}</Preview> : null}
|
||||
<Body style={bodyStyle}>
|
||||
<Container style={containerStyle}>
|
||||
<Row>
|
||||
<Column style={contentStyle}>
|
||||
<Section>
|
||||
<Hr style={brandRuleStyle} />
|
||||
<Text style={brandStyle}>{project_name}</Text>
|
||||
</Section>
|
||||
<Section>{children}</Section>
|
||||
<Hr style={footerRuleStyle} />
|
||||
<Section>
|
||||
<Text style={footerStyle}>
|
||||
© {new Date().getFullYear()} {project_name}. All rights
|
||||
reserved.
|
||||
</Text>
|
||||
</Section>
|
||||
</Column>
|
||||
</Row>
|
||||
</Container>
|
||||
</Body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
|
||||
const fontFamily =
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif'
|
||||
|
||||
const bodyStyle = {
|
||||
backgroundColor: "#f4f7f6",
|
||||
color: "#1e293b",
|
||||
fontFamily,
|
||||
margin: "0",
|
||||
padding: "32px 12px",
|
||||
}
|
||||
|
||||
const containerStyle = {
|
||||
backgroundColor: "#ffffff",
|
||||
border: "1px solid #dce7e5",
|
||||
borderRadius: "8px",
|
||||
boxSizing: "border-box" as const,
|
||||
margin: "0 auto",
|
||||
maxWidth: "560px",
|
||||
width: "100%",
|
||||
}
|
||||
|
||||
const contentStyle = {
|
||||
padding: "40px 36px 32px",
|
||||
}
|
||||
|
||||
const brandRuleStyle = {
|
||||
border: "0",
|
||||
borderTop: "3px solid #00897b",
|
||||
margin: "0 0 16px",
|
||||
width: "32px",
|
||||
}
|
||||
|
||||
const brandStyle = {
|
||||
color: "#334155",
|
||||
fontSize: "13px",
|
||||
fontWeight: "700",
|
||||
letterSpacing: "1px",
|
||||
lineHeight: "20px",
|
||||
margin: "0 0 32px",
|
||||
textTransform: "uppercase" as const,
|
||||
}
|
||||
|
||||
const footerRuleStyle = {
|
||||
border: "0",
|
||||
borderTop: "1px solid #dce7e5",
|
||||
margin: "36px 0 18px",
|
||||
width: "100%",
|
||||
}
|
||||
|
||||
const footerStyle = {
|
||||
color: "#64748b",
|
||||
fontSize: "12px",
|
||||
lineHeight: "20px",
|
||||
margin: "0",
|
||||
}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { Link as EmailLink } from "@react-email/components"
|
||||
|
||||
type LinkProps = React.PropsWithChildren & { href: string }
|
||||
|
||||
export const Link = ({ children, href }: LinkProps) => (
|
||||
<a href={href} className="text-brand-dark underline break-all">
|
||||
<EmailLink href={href} style={linkStyle}>
|
||||
{children}
|
||||
</a>
|
||||
</EmailLink>
|
||||
)
|
||||
|
||||
const linkStyle = {
|
||||
color: "#00695c",
|
||||
textDecoration: "underline",
|
||||
wordBreak: "break-all" as const,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user