major ui tweaks and refactoring

This commit is contained in:
Milo Schwartz 2025-01-04 20:22:01 -05:00
parent 51bf5c1408
commit 64158a823b
No known key found for this signature in database
91 changed files with 1791 additions and 1246 deletions

View file

@ -1,16 +1,20 @@
import {
Body,
Container,
Head,
Heading,
Html,
Preview,
Section,
Text,
Tailwind
} from "@react-email/components";
import * as React from "react";
import LetterHead from "./components/LetterHead";
import { themeColors } from "./lib/theme";
import {
EmailContainer,
EmailFooter,
EmailGreeting,
EmailHeading,
EmailLetterHead,
EmailText
} from "./components/Email";
interface Props {
email: string;
@ -23,41 +27,31 @@ export const ConfirmPasswordReset = ({ email }: Props) => {
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: {
primary: "#16A34A"
}
}
}
}}
>
<Tailwind config={themeColors}>
<Body className="font-sans relative">
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
<LetterHead />
<EmailContainer>
<EmailLetterHead />
<Heading className="text-2xl font-semibold text-gray-800 text-center">
Password Reset Confirmation
</Heading>
<Text className="text-base text-gray-700 mt-4">
Hi {email || "there"},
</Text>
<Text className="text-base text-gray-700 mt-2">
<EmailHeading>Password Reset Confirmation</EmailHeading>
<EmailGreeting>Hi {email || "there"},</EmailGreeting>
<EmailText>
This email confirms that your password has just been
reset. If you made this change, no further action is
required.
</Text>
<Text className="text-base text-gray-700 mt-2">
</EmailText>
<EmailText>
Thank you for keeping your account secure.
</Text>
<Text className="text-sm text-gray-500 mt-6">
</EmailText>
<EmailFooter>
Best regards,
<br />
Fossorial
</Text>
</Container>
</EmailFooter>
</EmailContainer>
</Body>
</Tailwind>
</Html>

View file

@ -1,16 +1,22 @@
import {
Body,
Container,
Head,
Heading,
Html,
Preview,
Section,
Text,
Tailwind
} from "@react-email/components";
import * as React from "react";
import LetterHead from "./components/LetterHead";
import { themeColors } from "./lib/theme";
import {
EmailContainer,
EmailFooter,
EmailGreeting,
EmailHeading,
EmailLetterHead,
EmailSection,
EmailText
} from "./components/Email";
import CopyCodeBox from "./components/CopyCodeBox";
interface Props {
email: string;
@ -25,50 +31,39 @@ export const ResetPasswordCode = ({ email, code, link }: Props) => {
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: {
primary: "#F97317"
}
}
}
}}
>
<Tailwind config={themeColors}>
<Body className="font-sans">
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
<LetterHead />
<EmailContainer>
<EmailLetterHead />
<Heading className="text-2xl font-semibold text-gray-800 text-center">
Password Reset Request
</Heading>
<Text className="text-base text-gray-700 mt-4">
Hi {email || "there"},
</Text>
<Text className="text-base text-gray-700 mt-2">
<EmailHeading>Password Reset Request</EmailHeading>
<EmailGreeting>Hi {email || "there"},</EmailGreeting>
<EmailText>
Youve requested to reset your password. Please{" "}
<a href={link} className="text-primary">
click here
</a>{" "}
and follow the instructions to reset your password,
or manually enter the following code:
</Text>
<Section className="text-center">
<Text className="inline-block bg-primary text-xl font-bold text-white py-2 px-4 border border-gray-300 rounded-xl">
{code}
</Text>
</Section>
<Text className="text-base text-gray-700 mt-2">
</EmailText>
<EmailSection>
<CopyCodeBox text={code} />
</EmailSection>
<EmailText>
If you didnt request this, you can safely ignore
this email.
</Text>
<Text className="text-sm text-gray-500 mt-6">
</EmailText>
<EmailFooter>
Best regards,
<br />
Fossorial
</Text>
</Container>
</EmailFooter>
</EmailContainer>
</Body>
</Tailwind>
</Html>

View file

@ -1,16 +1,22 @@
import {
Body,
Container,
Head,
Heading,
Html,
Preview,
Section,
Text,
Tailwind
} from "@react-email/components";
import * as React from "react";
import LetterHead from "./components/LetterHead";
import {
EmailContainer,
EmailLetterHead,
EmailHeading,
EmailText,
EmailFooter,
EmailSection,
EmailGreeting
} from "./components/Email";
import { themeColors } from "./lib/theme";
import CopyCodeBox from "./components/CopyCodeBox";
interface ResourceOTPCodeProps {
email?: string;
@ -31,44 +37,34 @@ export const ResourceOTPCode = ({
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: {
primary: "#F97317"
}
}
}
}}
>
<Tailwind config={themeColors}>
<Body className="font-sans">
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
<LetterHead />
<EmailContainer>
<EmailLetterHead />
<Heading className="text-2xl font-semibold text-gray-800 text-center">
<EmailHeading>
Your One-Time Password for {resourceName}
</Heading>
<Text className="text-base text-gray-700 mt-4">
Hi {email || "there"},
</Text>
<Text className="text-base text-gray-700 mt-2">
</EmailHeading>
<EmailGreeting>Hi {email || "there"},</EmailGreeting>
<EmailText>
Youve requested a one-time password to access{" "}
<strong>{resourceName}</strong> in{" "}
<strong>{organizationName}</strong>. Use the code
below to complete your authentication:
</Text>
<Section className="text-center">
<Text className="inline-block bg-primary text-xl font-bold text-white py-2 px-4 border border-gray-300 rounded-xl">
{otp}
</Text>
</Section>
<Text className="text-sm text-gray-500 mt-6">
</EmailText>
<EmailSection>
<CopyCodeBox text={otp} />
</EmailSection>
<EmailFooter>
Best regards,
<br />
Fossorial
</Text>
</Container>
</EmailFooter>
</EmailContainer>
</Body>
</Tailwind>
</Html>

View file

@ -1,17 +1,22 @@
import {
Body,
Container,
Head,
Heading,
Html,
Preview,
Section,
Text,
Tailwind,
Button
} from "@react-email/components";
import * as React from "react";
import LetterHead from "./components/LetterHead";
import { themeColors } from "./lib/theme";
import {
EmailContainer,
EmailFooter,
EmailGreeting,
EmailHeading,
EmailLetterHead,
EmailSection,
EmailText
} from "./components/Email";
import ButtonLink from "./components/ButtonLink";
interface SendInviteLinkProps {
email: string;
@ -34,55 +39,42 @@ export const SendInviteLink = ({
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: {
primary: "#F97317"
}
}
}
}}
>
<Tailwind config={themeColors}>
<Body className="font-sans">
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
<LetterHead />
<EmailContainer>
<EmailLetterHead />
<Heading className="text-2xl font-semibold text-gray-800 text-center">
Invited to Join {orgName}
</Heading>
<Text className="text-base text-gray-700 mt-4">
Hi {email || "there"},
</Text>
<Text className="text-base text-gray-700 mt-2">
<EmailHeading>Invited to Join {orgName}</EmailHeading>
<EmailGreeting>Hi {email || "there"},</EmailGreeting>
<EmailText>
Youve been invited to join the organization{" "}
{orgName}
<strong>{orgName}</strong>
{inviterName ? ` by ${inviterName}.` : "."} Please
access the link below to accept the invite.
</Text>
<Text className="text-base text-gray-700 mt-2">
</EmailText>
<EmailText>
This invite will expire in{" "}
<b>
<strong>
{expiresInDays}{" "}
{expiresInDays === "1" ? "day" : "days"}.
</b>
</Text>
<Section className="text-center">
<Button
href={inviteLink}
className="rounded-lg bg-primary px-[12px] py-[9px] text-center font-semibold text-white cursor-pointer text-xl"
>
Accept Invite to {orgName}
</Button>
</Section>
</strong>
</EmailText>
<Text className="text-sm text-gray-500 mt-6">
<EmailSection>
<ButtonLink href={inviteLink}>
Accept Invite to {orgName}
</ButtonLink>
</EmailSection>
<EmailFooter>
Best regards,
<br />
Fossorial
</Text>
</Container>
</EmailFooter>
</EmailContainer>
</Body>
</Tailwind>
</Html>

View file

@ -1,16 +1,20 @@
import {
Body,
Container,
Head,
Heading,
Html,
Preview,
Section,
Text,
Tailwind
} from "@react-email/components";
import * as React from "react";
import LetterHead from "./components/LetterHead";
import { themeColors } from "./lib/theme";
import {
EmailContainer,
EmailFooter,
EmailGreeting,
EmailHeading,
EmailLetterHead,
EmailText
} from "./components/Email";
interface Props {
email: string;
@ -24,52 +28,44 @@ export const TwoFactorAuthNotification = ({ email, enabled }: Props) => {
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: {
primary: "#16A34A"
}
}
}
}}
>
<Tailwind config={themeColors}>
<Body className="font-sans">
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
<LetterHead />
<EmailContainer>
<EmailLetterHead />
<Heading className="text-2xl font-semibold text-gray-800 text-center">
<EmailHeading>
Two-Factor Authentication{" "}
{enabled ? "Enabled" : "Disabled"}
</Heading>
<Text className="text-base text-gray-700 mt-4">
Hi {email || "there"},
</Text>
<Text className="text-base text-gray-700 mt-2">
</EmailHeading>
<EmailGreeting>Hi {email || "there"},</EmailGreeting>
<EmailText>
This email confirms that Two-Factor Authentication
has been successfully{" "}
{enabled ? "enabled" : "disabled"} on your account.
</Text>
</EmailText>
{enabled ? (
<Text className="text-base text-gray-700">
<EmailText>
With Two-Factor Authentication enabled, your
account is now more secure. Please ensure you
keep your authentication method safe.
</Text>
</EmailText>
) : (
<Text className="text-base text-gray-700">
<EmailText>
With Two-Factor Authentication disabled, your
account may be less secure. We recommend
enabling it to protect your account.
</Text>
</EmailText>
)}
<Text className="text-sm text-gray-500 mt-6">
<EmailFooter>
Best regards,
<br />
Fossorial
</Text>
</Container>
</EmailFooter>
</EmailContainer>
</Body>
</Tailwind>
</Html>

View file

@ -1,16 +1,22 @@
import {
Body,
Container,
Head,
Heading,
Html,
Preview,
Section,
Text,
Tailwind
} from "@react-email/components";
import * as React from "react";
import LetterHead from "./components/LetterHead";
import { themeColors } from "./lib/theme";
import {
EmailContainer,
EmailFooter,
EmailGreeting,
EmailHeading,
EmailLetterHead,
EmailSection,
EmailText
} from "./components/Email";
import CopyCodeBox from "./components/CopyCodeBox";
interface VerifyEmailProps {
username?: string;
@ -29,47 +35,36 @@ export const VerifyEmail = ({
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: {
primary: "#F97317"
}
}
}
}}
>
<Tailwind config={themeColors}>
<Body className="font-sans">
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
<LetterHead />
<EmailContainer>
<EmailLetterHead />
<Heading className="text-2xl font-semibold text-gray-800 text-center">
Please Verify Your Email
</Heading>
<Text className="text-base text-gray-700 mt-4">
Hi {username || "there"},
</Text>
<Text className="text-base text-gray-700 mt-2">
<EmailHeading>Please Verify Your Email</EmailHeading>
<EmailGreeting>Hi {username || "there"},</EmailGreeting>
<EmailText>
Youve requested to verify your email. Please use
the code below to complete the verification process
upon logging in.
</Text>
<Section className="text-center">
<Text className="inline-block bg-primary text-xl font-bold text-white py-2 px-4 border border-gray-300 rounded-xl">
{verificationCode}
</Text>
</Section>
<Text className="text-base text-gray-700 mt-2">
</EmailText>
<EmailSection>
<CopyCodeBox text={verificationCode} />
</EmailSection>
<EmailText>
If you didnt request this, you can safely ignore
this email.
</Text>
<Text className="text-sm text-gray-500 mt-6">
</EmailText>
<EmailFooter>
Best regards,
<br />
Fossorial
</Text>
</Container>
</EmailFooter>
</EmailContainer>
</Body>
</Tailwind>
</Html>

View file

@ -0,0 +1,18 @@
export default function ButtonLink({
href,
children,
className = ""
}: {
href: string;
children: React.ReactNode;
className?: string;
}) {
return (
<a
href={href}
className={`rounded-full bg-primary px-4 py-2 text-center font-semibold text-white text-xl no-underline inline-block ${className}`}
>
{children}
</a>
);
}

View file

@ -0,0 +1,11 @@
import React from "react";
export default function CopyCodeBox({ text }: { text: string }) {
return (
<div className="flex items-center justify-center rounded-lg bg-neutral-100 p-2">
<span className="text-2xl font-mono text-neutral-600 tracking-wide">
{text}
</span>
</div>
);
}

View file

@ -0,0 +1,91 @@
import { Container } from "@react-email/components";
import React from "react";
// EmailContainer: Wraps the entire email layout
export function EmailContainer({ children }: { children: React.ReactNode }) {
return (
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8 rounded-lg">
{children}
</Container>
);
}
// EmailLetterHead: For branding or logo at the top
export function EmailLetterHead() {
return (
<div className="mb-4">
<table
role="presentation"
width="100%"
style={{
marginBottom: "24px"
}}
>
<tr>
<td
style={{
fontSize: "14px",
fontWeight: "bold",
color: "#F97317"
}}
>
Pangolin
</td>
<td
style={{
fontSize: "14px",
textAlign: "right",
color: "#6B7280"
}}
>
{new Date().getFullYear()}
</td>
</tr>
</table>
</div>
);
}
// EmailHeading: For the primary message or headline
export function EmailHeading({ children }: { children: React.ReactNode }) {
return (
<h1 className="text-2xl font-semibold text-gray-800 text-center">
{children}
</h1>
);
}
export function EmailGreeting({ children }: { children: React.ReactNode }) {
return <p className="text-lg text-gray-700 my-4">{children}</p>;
}
// EmailText: For general text content
export function EmailText({
children,
className
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<p className={`my-2 text-base text-gray-700 ${className}`}>
{children}
</p>
);
}
// EmailSection: For visually distinct sections (like OTP)
export function EmailSection({
children,
className
}: {
children: React.ReactNode;
className?: string;
}) {
return <div className={`text-center my-4 ${className}`}>{children}</div>;
}
// EmailFooter: For closing or signature
export function EmailFooter({ children }: { children: React.ReactNode }) {
return <div className="text-sm text-gray-500 mt-6">{children}</div>;
}

View file

@ -1,36 +0,0 @@
import React from "react";
export function LetterHead() {
return (
<table
role="presentation"
width="100%"
style={{
marginBottom: "24px"
}}
>
<tr>
<td
style={{
fontSize: "14px",
fontWeight: "bold",
color: "#F97317"
}}
>
Pangolin
</td>
<td
style={{
fontSize: "14px",
textAlign: "right",
color: "#6B7280"
}}
>
{new Date().getFullYear()}
</td>
</tr>
</table>
);
}
export default LetterHead;

View file

@ -0,0 +1,9 @@
export const themeColors = {
theme: {
extend: {
colors: {
primary: "#F97317"
}
}
}
};