mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-10 20:02:26 +00:00
Pull up downstream changes
This commit is contained in:
@@ -2,6 +2,7 @@ import { render } from "@react-email/render";
|
||||
import { ReactElement } from "react";
|
||||
import emailClient from "@server/emails";
|
||||
import logger from "@server/logger";
|
||||
import config from "@server/lib/config";
|
||||
|
||||
export async function sendEmail(
|
||||
template: ReactElement,
|
||||
@@ -24,9 +25,11 @@ export async function sendEmail(
|
||||
|
||||
const emailHtml = await render(template);
|
||||
|
||||
const appName = "Fossorial - Pangolin";
|
||||
|
||||
await emailClient.sendMail({
|
||||
from: {
|
||||
name: opts.name || "Pangolin",
|
||||
name: opts.name || appName,
|
||||
address: opts.from,
|
||||
},
|
||||
to: opts.to,
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import {
|
||||
Body,
|
||||
Head,
|
||||
Html,
|
||||
Preview,
|
||||
Tailwind
|
||||
} from "@react-email/components";
|
||||
import * as React from "react";
|
||||
import React from "react";
|
||||
import { Body, Head, Html, Preview, Tailwind } from "@react-email/components";
|
||||
import { themeColors } from "./lib/theme";
|
||||
import {
|
||||
EmailContainer,
|
||||
@@ -22,29 +16,29 @@ interface Props {
|
||||
}
|
||||
|
||||
export const ConfirmPasswordReset = ({ email }: Props) => {
|
||||
const previewText = `Your password has been reset`;
|
||||
const previewText = `Your password has been successfully reset.`;
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind config={themeColors}>
|
||||
<Body className="font-sans relative">
|
||||
<Body className="font-sans bg-gray-50">
|
||||
<EmailContainer>
|
||||
<EmailLetterHead />
|
||||
|
||||
<EmailHeading>Password Reset Confirmation</EmailHeading>
|
||||
{/* <EmailHeading>Password Successfully Reset</EmailHeading> */}
|
||||
|
||||
<EmailGreeting>Hi {email || "there"},</EmailGreeting>
|
||||
<EmailGreeting>Hi there,</EmailGreeting>
|
||||
|
||||
<EmailText>
|
||||
This email confirms that your password has just been
|
||||
reset. If you made this change, no further action is
|
||||
required.
|
||||
Your password has been successfully reset. You can
|
||||
now sign in to your account using your new password.
|
||||
</EmailText>
|
||||
|
||||
<EmailText>
|
||||
Thank you for keeping your account secure.
|
||||
If you didn't make this change, please contact our
|
||||
support team immediately to secure your account.
|
||||
</EmailText>
|
||||
|
||||
<EmailFooter>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import {
|
||||
Body,
|
||||
Head,
|
||||
Html,
|
||||
Preview,
|
||||
Tailwind
|
||||
} from "@react-email/components";
|
||||
import * as React from "react";
|
||||
import React from "react";
|
||||
import { Body, Head, Html, Preview, Tailwind } from "@react-email/components";
|
||||
import { themeColors } from "./lib/theme";
|
||||
import {
|
||||
EmailContainer,
|
||||
@@ -18,6 +12,7 @@ import {
|
||||
EmailText
|
||||
} from "./components/Email";
|
||||
import CopyCodeBox from "./components/CopyCodeBox";
|
||||
import ButtonLink from "./components/ButtonLink";
|
||||
|
||||
interface Props {
|
||||
email: string;
|
||||
@@ -26,37 +21,39 @@ interface Props {
|
||||
}
|
||||
|
||||
export const ResetPasswordCode = ({ email, code, link }: Props) => {
|
||||
const previewText = `Your password reset code is ${code}`;
|
||||
const previewText = `Reset your password with code: ${code}`;
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind config={themeColors}>
|
||||
<Body className="font-sans">
|
||||
<Body className="font-sans bg-gray-50">
|
||||
<EmailContainer>
|
||||
<EmailLetterHead />
|
||||
|
||||
<EmailHeading>Password Reset Request</EmailHeading>
|
||||
{/* <EmailHeading>Reset Your Password</EmailHeading> */}
|
||||
|
||||
<EmailGreeting>Hi {email || "there"},</EmailGreeting>
|
||||
<EmailGreeting>Hi there,</EmailGreeting>
|
||||
|
||||
<EmailText>
|
||||
You’ve 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:
|
||||
You've requested to reset your password. Click the
|
||||
button below to reset your password, or use the
|
||||
verification code provided if prompted.
|
||||
</EmailText>
|
||||
|
||||
<EmailSection>
|
||||
<ButtonLink href={link}>Reset Password</ButtonLink>
|
||||
</EmailSection>
|
||||
|
||||
<EmailSection>
|
||||
<CopyCodeBox text={code} />
|
||||
</EmailSection>
|
||||
|
||||
<EmailText>
|
||||
If you didn’t request this, you can safely ignore
|
||||
this email.
|
||||
This reset code will expire in 2 hours. If you
|
||||
didn't request a password reset, you can safely
|
||||
ignore this email.
|
||||
</EmailText>
|
||||
|
||||
<EmailFooter>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import {
|
||||
Body,
|
||||
Head,
|
||||
Html,
|
||||
Preview,
|
||||
Tailwind
|
||||
} from "@react-email/components";
|
||||
import * as React from "react";
|
||||
import React from "react";
|
||||
import { Body, Head, Html, Preview, Tailwind } from "@react-email/components";
|
||||
import {
|
||||
EmailContainer,
|
||||
EmailLetterHead,
|
||||
@@ -32,34 +26,40 @@ export const ResourceOTPCode = ({
|
||||
orgName: organizationName,
|
||||
otp
|
||||
}: ResourceOTPCodeProps) => {
|
||||
const previewText = `Your one-time password for ${resourceName} is ${otp}`;
|
||||
const previewText = `Your access code for ${resourceName}: ${otp}`;
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind config={themeColors}>
|
||||
<Body className="font-sans">
|
||||
<Body className="font-sans bg-gray-50">
|
||||
<EmailContainer>
|
||||
<EmailLetterHead />
|
||||
|
||||
<EmailHeading>
|
||||
Your One-Time Code for {resourceName}
|
||||
</EmailHeading>
|
||||
{/* <EmailHeading> */}
|
||||
{/* Access Code for {resourceName} */}
|
||||
{/* </EmailHeading> */}
|
||||
|
||||
<EmailGreeting>Hi {email || "there"},</EmailGreeting>
|
||||
<EmailGreeting>Hi there,</EmailGreeting>
|
||||
|
||||
<EmailText>
|
||||
You’ve requested a one-time password to access{" "}
|
||||
You've requested access to{" "}
|
||||
<strong>{resourceName}</strong> in{" "}
|
||||
<strong>{organizationName}</strong>. Use the code
|
||||
below to complete your authentication:
|
||||
<strong>{organizationName}</strong>. Use the
|
||||
verification code below to complete your
|
||||
authentication.
|
||||
</EmailText>
|
||||
|
||||
<EmailSection>
|
||||
<CopyCodeBox text={otp} />
|
||||
</EmailSection>
|
||||
|
||||
<EmailText>
|
||||
This code will expire in 15 minutes. If you didn't
|
||||
request this code, please ignore this email.
|
||||
</EmailText>
|
||||
|
||||
<EmailFooter>
|
||||
<EmailSignature />
|
||||
</EmailFooter>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import {
|
||||
Body,
|
||||
Head,
|
||||
Html,
|
||||
Preview,
|
||||
Tailwind,
|
||||
} from "@react-email/components";
|
||||
import * as React from "react";
|
||||
import React from "react";
|
||||
import { Body, Head, Html, Preview, Tailwind } from "@react-email/components";
|
||||
import { themeColors } from "./lib/theme";
|
||||
import {
|
||||
EmailContainer,
|
||||
@@ -41,35 +35,44 @@ export const SendInviteLink = ({
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind config={themeColors}>
|
||||
<Body className="font-sans">
|
||||
<Body className="font-sans bg-gray-50">
|
||||
<EmailContainer>
|
||||
<EmailLetterHead />
|
||||
|
||||
<EmailHeading>Invited to Join {orgName}</EmailHeading>
|
||||
{/* <EmailHeading> */}
|
||||
{/* You're Invited to Join {orgName} */}
|
||||
{/* </EmailHeading> */}
|
||||
|
||||
<EmailGreeting>Hi {email || "there"},</EmailGreeting>
|
||||
<EmailGreeting>Hi there,</EmailGreeting>
|
||||
|
||||
<EmailText>
|
||||
You’ve been invited to join the organization{" "}
|
||||
You've been invited to join{" "}
|
||||
<strong>{orgName}</strong>
|
||||
{inviterName ? ` by ${inviterName}.` : "."} Please
|
||||
access the link below to accept the invite.
|
||||
</EmailText>
|
||||
|
||||
<EmailText>
|
||||
This invite will expire in{" "}
|
||||
<strong>
|
||||
{expiresInDays}{" "}
|
||||
{expiresInDays === "1" ? "day" : "days"}.
|
||||
</strong>
|
||||
{inviterName ? ` by ${inviterName}` : ""}. Click the
|
||||
button below to accept your invitation and get
|
||||
started.
|
||||
</EmailText>
|
||||
|
||||
<EmailSection>
|
||||
<ButtonLink href={inviteLink}>
|
||||
Accept Invite to {orgName}
|
||||
Accept Invitation
|
||||
</ButtonLink>
|
||||
</EmailSection>
|
||||
|
||||
{/* <EmailText> */}
|
||||
{/* If you're having trouble clicking the button, copy */}
|
||||
{/* and paste the URL below into your web browser: */}
|
||||
{/* <br /> */}
|
||||
{/* <span className="break-all">{inviteLink}</span> */}
|
||||
{/* </EmailText> */}
|
||||
|
||||
<EmailText>
|
||||
This invite expires in {expiresInDays}{" "}
|
||||
{expiresInDays === "1" ? "day" : "days"}. If the
|
||||
link has expired, please contact the owner of the
|
||||
organization to request a new invitation.
|
||||
</EmailText>
|
||||
|
||||
<EmailFooter>
|
||||
<EmailSignature />
|
||||
</EmailFooter>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import {
|
||||
Body,
|
||||
Head,
|
||||
Html,
|
||||
Preview,
|
||||
Tailwind
|
||||
} from "@react-email/components";
|
||||
import * as React from "react";
|
||||
import React from "react";
|
||||
import { Body, Head, Html, Preview, Tailwind } from "@react-email/components";
|
||||
import { themeColors } from "./lib/theme";
|
||||
import {
|
||||
EmailContainer,
|
||||
@@ -23,44 +17,52 @@ interface Props {
|
||||
}
|
||||
|
||||
export const TwoFactorAuthNotification = ({ email, enabled }: Props) => {
|
||||
const previewText = `Two-Factor Authentication has been ${enabled ? "enabled" : "disabled"}`;
|
||||
const previewText = `Two-Factor Authentication ${enabled ? "enabled" : "disabled"} for your account`;
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind config={themeColors}>
|
||||
<Body className="font-sans">
|
||||
<Body className="font-sans bg-gray-50">
|
||||
<EmailContainer>
|
||||
<EmailLetterHead />
|
||||
|
||||
<EmailHeading>
|
||||
Two-Factor Authentication{" "}
|
||||
{enabled ? "Enabled" : "Disabled"}
|
||||
</EmailHeading>
|
||||
{/* <EmailHeading> */}
|
||||
{/* Security Update: 2FA{" "} */}
|
||||
{/* {enabled ? "Enabled" : "Disabled"} */}
|
||||
{/* </EmailHeading> */}
|
||||
|
||||
<EmailGreeting>Hi {email || "there"},</EmailGreeting>
|
||||
<EmailGreeting>Hi there,</EmailGreeting>
|
||||
|
||||
<EmailText>
|
||||
This email confirms that Two-Factor Authentication
|
||||
has been successfully{" "}
|
||||
{enabled ? "enabled" : "disabled"} on your account.
|
||||
Two-factor authentication has been successfully{" "}
|
||||
<strong>{enabled ? "enabled" : "disabled"}</strong>{" "}
|
||||
on your account.
|
||||
</EmailText>
|
||||
|
||||
{enabled ? (
|
||||
<EmailText>
|
||||
With Two-Factor Authentication enabled, your
|
||||
account is now more secure. Please ensure you
|
||||
keep your authentication method safe.
|
||||
</EmailText>
|
||||
<>
|
||||
<EmailText>
|
||||
Your account is now protected with an
|
||||
additional layer of security. Keep your
|
||||
authentication method safe and accessible.
|
||||
</EmailText>
|
||||
</>
|
||||
) : (
|
||||
<EmailText>
|
||||
With Two-Factor Authentication disabled, your
|
||||
account may be less secure. We recommend
|
||||
enabling it to protect your account.
|
||||
</EmailText>
|
||||
<>
|
||||
<EmailText>
|
||||
We recommend re-enabling two-factor
|
||||
authentication to keep your account secure.
|
||||
</EmailText>
|
||||
</>
|
||||
)}
|
||||
|
||||
<EmailText>
|
||||
If you didn't make this change, please contact our
|
||||
support team immediately.
|
||||
</EmailText>
|
||||
|
||||
<EmailFooter>
|
||||
<EmailSignature />
|
||||
</EmailFooter>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { Body, Head, Html, Preview, Tailwind } from "@react-email/components";
|
||||
import * as React from "react";
|
||||
import { themeColors } from "./lib/theme";
|
||||
import {
|
||||
EmailContainer,
|
||||
@@ -24,25 +24,24 @@ export const VerifyEmail = ({
|
||||
verificationCode,
|
||||
verifyLink
|
||||
}: VerifyEmailProps) => {
|
||||
const previewText = `Your verification code is ${verificationCode}`;
|
||||
const previewText = `Verify your email with code: ${verificationCode}`;
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind config={themeColors}>
|
||||
<Body className="font-sans">
|
||||
<Body className="font-sans bg-gray-50">
|
||||
<EmailContainer>
|
||||
<EmailLetterHead />
|
||||
|
||||
<EmailHeading>Please Verify Your Email</EmailHeading>
|
||||
{/* <EmailHeading>Verify Your Email Address</EmailHeading> */}
|
||||
|
||||
<EmailGreeting>Hi {username || "there"},</EmailGreeting>
|
||||
<EmailGreeting>Hi there,</EmailGreeting>
|
||||
|
||||
<EmailText>
|
||||
You’ve requested to verify your email. Please use
|
||||
the code below to complete the verification process
|
||||
upon logging in.
|
||||
Welcome! To complete your account setup, please
|
||||
verify your email address using the code below.
|
||||
</EmailText>
|
||||
|
||||
<EmailSection>
|
||||
@@ -50,7 +49,8 @@ export const VerifyEmail = ({
|
||||
</EmailSection>
|
||||
|
||||
<EmailText>
|
||||
If you didn’t request this, you can safely ignore
|
||||
This verification code will expire in 15 minutes. If
|
||||
you didn't create an account, you can safely ignore
|
||||
this email.
|
||||
</EmailText>
|
||||
|
||||
|
||||
131
server/emails/templates/WelcomeQuickStart.tsx
Normal file
131
server/emails/templates/WelcomeQuickStart.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import React from "react";
|
||||
import { Body, Head, Html, Preview, Tailwind } from "@react-email/components";
|
||||
import { themeColors } from "./lib/theme";
|
||||
import {
|
||||
EmailContainer,
|
||||
EmailFooter,
|
||||
EmailGreeting,
|
||||
EmailHeading,
|
||||
EmailLetterHead,
|
||||
EmailSection,
|
||||
EmailSignature,
|
||||
EmailText,
|
||||
EmailInfoSection
|
||||
} from "./components/Email";
|
||||
import ButtonLink from "./components/ButtonLink";
|
||||
import CopyCodeBox from "./components/CopyCodeBox";
|
||||
|
||||
interface WelcomeQuickStartProps {
|
||||
username?: string;
|
||||
link: string;
|
||||
fallbackLink: string;
|
||||
resourceMethod: string;
|
||||
resourceHostname: string;
|
||||
resourcePort: string | number;
|
||||
resourceUrl: string;
|
||||
cliCommand: string;
|
||||
}
|
||||
|
||||
export const WelcomeQuickStart = ({
|
||||
username,
|
||||
link,
|
||||
fallbackLink,
|
||||
resourceMethod,
|
||||
resourceHostname,
|
||||
resourcePort,
|
||||
resourceUrl,
|
||||
cliCommand
|
||||
}: WelcomeQuickStartProps) => {
|
||||
const previewText = "Welcome! Here's what to do next";
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind config={themeColors}>
|
||||
<Body className="font-sans bg-gray-50">
|
||||
<EmailContainer>
|
||||
<EmailLetterHead />
|
||||
|
||||
<EmailGreeting>Hi there,</EmailGreeting>
|
||||
|
||||
<EmailText>
|
||||
Thank you for trying out Pangolin! We're excited to
|
||||
have you on board.
|
||||
</EmailText>
|
||||
|
||||
<EmailText>
|
||||
To continue to configure your site, resources, and
|
||||
other features, complete your account setup to
|
||||
access the full dashboard.
|
||||
</EmailText>
|
||||
|
||||
<EmailSection>
|
||||
<ButtonLink href={link}>
|
||||
View Your Dashboard
|
||||
</ButtonLink>
|
||||
{/* <p className="text-sm text-gray-300 mt-2"> */}
|
||||
{/* If the button above doesn't work, you can also */}
|
||||
{/* use this{" "} */}
|
||||
{/* <a href={fallbackLink} className="underline"> */}
|
||||
{/* link */}
|
||||
{/* </a> */}
|
||||
{/* . */}
|
||||
{/* </p> */}
|
||||
</EmailSection>
|
||||
|
||||
<EmailSection>
|
||||
<div className="mb-2 font-semibold text-gray-900 text-base text-left">
|
||||
Connect your site using Newt
|
||||
</div>
|
||||
<div className="inline-block w-full">
|
||||
<div className="bg-gray-50 border border-gray-200 rounded-lg px-6 py-4 mx-auto text-left">
|
||||
<span className="text-sm font-mono text-gray-900 tracking-wider">
|
||||
{cliCommand}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
To learn how to use Newt, including more
|
||||
installation methods, visit the{" "}
|
||||
<a
|
||||
href="https://docs.fossorial.io"
|
||||
className="underline"
|
||||
>
|
||||
docs
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</EmailSection>
|
||||
|
||||
<EmailInfoSection
|
||||
title="Your Demo Resource"
|
||||
items={[
|
||||
{ label: "Method", value: resourceMethod },
|
||||
{ label: "Hostname", value: resourceHostname },
|
||||
{ label: "Port", value: resourcePort },
|
||||
{
|
||||
label: "Resource URL",
|
||||
value: (
|
||||
<a
|
||||
href={resourceUrl}
|
||||
className="underline text-blue-600"
|
||||
>
|
||||
{resourceUrl}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<EmailFooter>
|
||||
<EmailSignature />
|
||||
</EmailFooter>
|
||||
</EmailContainer>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
export default WelcomeQuickStart;
|
||||
@@ -12,7 +12,11 @@ export default function ButtonLink({
|
||||
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}`}
|
||||
className={`inline-block bg-primary hover:bg-primary/90 text-white font-semibold px-8 py-3 rounded-lg text-center no-underline transition-colors ${className}`}
|
||||
style={{
|
||||
backgroundColor: "#F97316",
|
||||
textDecoration: "none"
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
|
||||
@@ -2,10 +2,15 @@ import React from "react";
|
||||
|
||||
export default function CopyCodeBox({ text }: { text: string }) {
|
||||
return (
|
||||
<div className="text-center rounded-lg bg-neutral-100 p-2">
|
||||
<span className="text-2xl font-mono text-neutral-600 tracking-wide">
|
||||
{text}
|
||||
</span>
|
||||
<div className="inline-block">
|
||||
<div className="bg-gray-50 border border-gray-200 rounded-lg px-6 py-4 mx-auto">
|
||||
<span className="text-2xl font-mono text-gray-900 tracking-wider font-semibold">
|
||||
{text}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
Copy and paste this code when prompted
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,47 +1,26 @@
|
||||
import { Container } from "@react-email/components";
|
||||
import React from "react";
|
||||
import { Container, Img } from "@react-email/components";
|
||||
|
||||
// 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">
|
||||
<Container className="bg-white border border-solid border-gray-200 max-w-lg mx-auto my-8 rounded-lg overflow-hidden shadow-sm">
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
// EmailLetterHead: For branding or logo at the top
|
||||
// EmailLetterHead: For branding with logo on dark background
|
||||
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 className="px-6 pt-8 pb-2 text-center">
|
||||
<Img
|
||||
src="https://fossorial-public-assets.s3.us-east-1.amazonaws.com/word_mark_black.png"
|
||||
alt="Fossorial"
|
||||
width="120"
|
||||
height="auto"
|
||||
className="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -49,14 +28,22 @@ export function EmailLetterHead() {
|
||||
// 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>
|
||||
<div className="px-6 pt-4 pb-1">
|
||||
<h1 className="text-2xl font-semibold text-gray-900 text-center leading-tight">
|
||||
{children}
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function EmailGreeting({ children }: { children: React.ReactNode }) {
|
||||
return <p className="text-base text-gray-700 my-4">{children}</p>;
|
||||
return (
|
||||
<div className="px-6">
|
||||
<p className="text-base text-gray-700 leading-relaxed">
|
||||
{children}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// EmailText: For general text content
|
||||
@@ -68,9 +55,13 @@ export function EmailText({
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<p className={`my-2 text-base text-gray-700 ${className}`}>
|
||||
{children}
|
||||
</p>
|
||||
<div className="px-6">
|
||||
<p
|
||||
className={`text-base text-gray-700 leading-relaxed ${className}`}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -82,20 +73,70 @@ export function EmailSection({
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return <div className={`text-center my-6 ${className}`}>{children}</div>;
|
||||
return (
|
||||
<div className={`px-6 py-6 text-center ${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>;
|
||||
return (
|
||||
<div className="px-6 py-6 border-t border-gray-100 bg-gray-50">
|
||||
{children}
|
||||
<p className="text-xs text-gray-400 mt-4">
|
||||
For any questions or support, please contact us at:
|
||||
<br />
|
||||
support@fossorial.io
|
||||
</p>
|
||||
<p className="text-xs text-gray-300 text-center mt-4">
|
||||
© {new Date().getFullYear()} Fossorial, Inc. All rights
|
||||
reserved.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function EmailSignature() {
|
||||
return (
|
||||
<p>
|
||||
Best regards,
|
||||
<br />
|
||||
Fossorial
|
||||
</p>
|
||||
<div className="text-sm text-gray-600">
|
||||
<p className="mb-2">
|
||||
Best regards,
|
||||
<br />
|
||||
<strong>The Fossorial Team</strong>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// EmailInfoSection: For structured key-value info (like resource details)
|
||||
export function EmailInfoSection({
|
||||
title,
|
||||
items
|
||||
}: {
|
||||
title?: string;
|
||||
items: { label: string; value: React.ReactNode }[];
|
||||
}) {
|
||||
return (
|
||||
<div className="px-6 py-4">
|
||||
{title && (
|
||||
<div className="mb-2 font-semibold text-gray-900 text-base">
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<table className="w-full text-sm text-left">
|
||||
<tbody>
|
||||
{items.map((item, idx) => (
|
||||
<tr key={idx}>
|
||||
<td className="pr-4 py-1 text-gray-600 align-top whitespace-nowrap">
|
||||
{item.label}
|
||||
</td>
|
||||
<td className="py-1 text-gray-900 break-all">
|
||||
{item.value}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import React from "react";
|
||||
|
||||
export const themeColors = {
|
||||
theme: {
|
||||
extend: {
|
||||
|
||||
Reference in New Issue
Block a user