@jl0810/email-templates 1.0.0
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/dist/branding.d.ts +26 -0
- package/dist/branding.js +35 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +110 -0
- package/dist/templates/magic-link.d.ts +8 -0
- package/dist/templates/magic-link.js +109 -0
- package/dist/templates/password-reset.d.ts +9 -0
- package/dist/templates/password-reset.js +128 -0
- package/dist/templates/welcome.d.ts +8 -0
- package/dist/templates/welcome.js +134 -0
- package/package.json +40 -0
- package/raydoug-email-templates-1.0.0.tgz +0 -0
- package/src/branding.ts +65 -0
- package/src/index.ts +164 -0
- package/src/templates/magic-link.tsx +228 -0
- package/src/templates/password-reset.tsx +260 -0
- package/src/templates/welcome.tsx +268 -0
- package/test-send.ts +52 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branding configuration for email templates
|
|
3
|
+
* Each app can provide its own branding
|
|
4
|
+
*/
|
|
5
|
+
export interface AppBranding {
|
|
6
|
+
/** App name (e.g., "CardsGoneCrazy", "RetirementPlanner", "FakeSharp") */
|
|
7
|
+
appName: string;
|
|
8
|
+
/** Logo URL (absolute URL) */
|
|
9
|
+
logoUrl: string;
|
|
10
|
+
/** Primary brand color (hex, e.g., "#7C3AED") */
|
|
11
|
+
primaryColor: string;
|
|
12
|
+
/** Secondary/accent color (hex) */
|
|
13
|
+
accentColor?: string;
|
|
14
|
+
/** Support email address */
|
|
15
|
+
supportEmail: string;
|
|
16
|
+
/** App website URL */
|
|
17
|
+
websiteUrl: string;
|
|
18
|
+
/** Footer text */
|
|
19
|
+
footerText?: string;
|
|
20
|
+
/** CSS Gradient for the logo icon (e.g., "linear-gradient(to bottom right, #06B5D4, #3B82F6)") */
|
|
21
|
+
logoGradient?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Pre-configured branding for RayDoug apps
|
|
25
|
+
*/
|
|
26
|
+
export declare const APP_BRANDING: Record<string, AppBranding>;
|
package/dist/branding.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-configured branding for RayDoug apps
|
|
3
|
+
*/
|
|
4
|
+
export const APP_BRANDING = {
|
|
5
|
+
cards: {
|
|
6
|
+
appName: "CardsGoneCrazy",
|
|
7
|
+
logoUrl: "",
|
|
8
|
+
primaryColor: "#06B5D4", // Cyan-500
|
|
9
|
+
accentColor: "#3B82F6", // Blue-500
|
|
10
|
+
logoGradient: "linear-gradient(135deg, #06B5D4 0%, #2563EB 100%)", // Cyan-500 to Blue-600
|
|
11
|
+
supportEmail: "support@cardsgonecrazy.com",
|
|
12
|
+
websiteUrl: "https://cardsgonecrazy.com",
|
|
13
|
+
footerText: "© CardsGoneCrazy. All rights reserved.",
|
|
14
|
+
},
|
|
15
|
+
retirement: {
|
|
16
|
+
appName: "RouteMyRetirement",
|
|
17
|
+
logoUrl: "",
|
|
18
|
+
primaryColor: "#2563EB", // Blue-600
|
|
19
|
+
accentColor: "#6366F1", // Indigo-500
|
|
20
|
+
logoGradient: "linear-gradient(135deg, #2563EB 0%, #4F46E5 100%)", // Blue-600 to Indigo-600
|
|
21
|
+
supportEmail: "support@routemyretirement.com",
|
|
22
|
+
websiteUrl: "https://routemyretirement.com",
|
|
23
|
+
footerText: "© RouteMyRetirement. All rights reserved.",
|
|
24
|
+
},
|
|
25
|
+
fakesharp: {
|
|
26
|
+
appName: "FakeSharp",
|
|
27
|
+
logoUrl: "",
|
|
28
|
+
primaryColor: "#10B981", // Emerald-500
|
|
29
|
+
accentColor: "#3B82F6", // Blue-500 for the logo gradient
|
|
30
|
+
logoGradient: "linear-gradient(135deg, #10B981 0%, #3B82F6 100%)", // Emerald-500 to Blue-500
|
|
31
|
+
supportEmail: "support@fakesharp.com",
|
|
32
|
+
websiteUrl: "https://fakesharp.com",
|
|
33
|
+
footerText: "© FakeSharp. All rights reserved.",
|
|
34
|
+
},
|
|
35
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { render } from "@react-email/components";
|
|
2
|
+
export * from "./branding";
|
|
3
|
+
export { MagicLinkEmail } from "./templates/magic-link";
|
|
4
|
+
export { WelcomeEmail } from "./templates/welcome";
|
|
5
|
+
export { PasswordResetEmail } from "./templates/password-reset";
|
|
6
|
+
export { render };
|
|
7
|
+
import type { AppBranding } from "./branding";
|
|
8
|
+
/**
|
|
9
|
+
* Helper to get branding by app key
|
|
10
|
+
*/
|
|
11
|
+
export declare function getBranding(appKey: "cards" | "retirement" | "fakesharp"): AppBranding;
|
|
12
|
+
/**
|
|
13
|
+
* Render magic link email to HTML
|
|
14
|
+
*/
|
|
15
|
+
export declare function renderMagicLinkEmail(options: {
|
|
16
|
+
magicLink: string;
|
|
17
|
+
branding: AppBranding;
|
|
18
|
+
userEmail?: string;
|
|
19
|
+
}): Promise<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Render welcome email to HTML
|
|
22
|
+
*/
|
|
23
|
+
export declare function renderWelcomeEmail(options: {
|
|
24
|
+
branding: AppBranding;
|
|
25
|
+
userName?: string;
|
|
26
|
+
dashboardUrl?: string;
|
|
27
|
+
}): Promise<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Render password reset email to HTML
|
|
30
|
+
*/
|
|
31
|
+
export declare function renderPasswordResetEmail(options: {
|
|
32
|
+
resetLink: string;
|
|
33
|
+
branding: AppBranding;
|
|
34
|
+
userEmail?: string;
|
|
35
|
+
expiresIn?: string;
|
|
36
|
+
}): Promise<string>;
|
|
37
|
+
/**
|
|
38
|
+
* useSend API helper - sends email via useSend
|
|
39
|
+
*/
|
|
40
|
+
export declare function sendEmail(options: {
|
|
41
|
+
to: string;
|
|
42
|
+
from: string;
|
|
43
|
+
subject: string;
|
|
44
|
+
html: string;
|
|
45
|
+
apiKey: string;
|
|
46
|
+
apiUrl?: string;
|
|
47
|
+
}): Promise<{
|
|
48
|
+
emailId: string;
|
|
49
|
+
}>;
|
|
50
|
+
/**
|
|
51
|
+
* High-level helper: Send magic link email
|
|
52
|
+
*/
|
|
53
|
+
export declare function sendMagicLinkEmail(options: {
|
|
54
|
+
to: string;
|
|
55
|
+
magicLink: string;
|
|
56
|
+
branding: AppBranding;
|
|
57
|
+
apiKey: string;
|
|
58
|
+
}): Promise<{
|
|
59
|
+
emailId: string;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* High-level helper: Send welcome email
|
|
63
|
+
*/
|
|
64
|
+
export declare function sendWelcomeEmail(options: {
|
|
65
|
+
to: string;
|
|
66
|
+
userName?: string;
|
|
67
|
+
branding: AppBranding;
|
|
68
|
+
apiKey: string;
|
|
69
|
+
dashboardUrl?: string;
|
|
70
|
+
}): Promise<{
|
|
71
|
+
emailId: string;
|
|
72
|
+
}>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { render } from "@react-email/components";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
// Export branding
|
|
4
|
+
export * from "./branding";
|
|
5
|
+
// Export templates
|
|
6
|
+
export { MagicLinkEmail } from "./templates/magic-link";
|
|
7
|
+
export { WelcomeEmail } from "./templates/welcome";
|
|
8
|
+
export { PasswordResetEmail } from "./templates/password-reset";
|
|
9
|
+
// Re-export render for convenience
|
|
10
|
+
export { render };
|
|
11
|
+
// Import templates for the helper functions
|
|
12
|
+
import { MagicLinkEmail } from "./templates/magic-link";
|
|
13
|
+
import { WelcomeEmail } from "./templates/welcome";
|
|
14
|
+
import { PasswordResetEmail } from "./templates/password-reset";
|
|
15
|
+
import { APP_BRANDING } from "./branding";
|
|
16
|
+
/**
|
|
17
|
+
* Helper to get branding by app key
|
|
18
|
+
*/
|
|
19
|
+
export function getBranding(appKey) {
|
|
20
|
+
return APP_BRANDING[appKey];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Render magic link email to HTML
|
|
24
|
+
*/
|
|
25
|
+
export async function renderMagicLinkEmail(options) {
|
|
26
|
+
return render(React.createElement(MagicLinkEmail, {
|
|
27
|
+
magicLink: options.magicLink,
|
|
28
|
+
branding: options.branding,
|
|
29
|
+
userEmail: options.userEmail,
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Render welcome email to HTML
|
|
34
|
+
*/
|
|
35
|
+
export async function renderWelcomeEmail(options) {
|
|
36
|
+
return render(React.createElement(WelcomeEmail, {
|
|
37
|
+
branding: options.branding,
|
|
38
|
+
userName: options.userName,
|
|
39
|
+
dashboardUrl: options.dashboardUrl,
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Render password reset email to HTML
|
|
44
|
+
*/
|
|
45
|
+
export async function renderPasswordResetEmail(options) {
|
|
46
|
+
return render(React.createElement(PasswordResetEmail, {
|
|
47
|
+
resetLink: options.resetLink,
|
|
48
|
+
branding: options.branding,
|
|
49
|
+
userEmail: options.userEmail,
|
|
50
|
+
expiresIn: options.expiresIn,
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* useSend API helper - sends email via useSend
|
|
55
|
+
*/
|
|
56
|
+
export async function sendEmail(options) {
|
|
57
|
+
const baseUrl = options.apiUrl || "https://mail.raydoug.com/api/v1";
|
|
58
|
+
const response = await fetch(`${baseUrl}/emails`, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: {
|
|
61
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
62
|
+
"Content-Type": "application/json",
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify({
|
|
65
|
+
to: options.to,
|
|
66
|
+
from: options.from,
|
|
67
|
+
subject: options.subject,
|
|
68
|
+
html: options.html,
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const error = await response.json().catch(() => ({}));
|
|
73
|
+
throw new Error(`Failed to send email: ${response.statusText} - ${JSON.stringify(error)}`);
|
|
74
|
+
}
|
|
75
|
+
return response.json();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* High-level helper: Send magic link email
|
|
79
|
+
*/
|
|
80
|
+
export async function sendMagicLinkEmail(options) {
|
|
81
|
+
const html = await renderMagicLinkEmail({
|
|
82
|
+
magicLink: options.magicLink,
|
|
83
|
+
branding: options.branding,
|
|
84
|
+
userEmail: options.to,
|
|
85
|
+
});
|
|
86
|
+
return sendEmail({
|
|
87
|
+
to: options.to,
|
|
88
|
+
from: options.branding.supportEmail.replace("support@", "noreply@"),
|
|
89
|
+
subject: `Sign in to ${options.branding.appName}`,
|
|
90
|
+
html,
|
|
91
|
+
apiKey: options.apiKey,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* High-level helper: Send welcome email
|
|
96
|
+
*/
|
|
97
|
+
export async function sendWelcomeEmail(options) {
|
|
98
|
+
const html = await renderWelcomeEmail({
|
|
99
|
+
branding: options.branding,
|
|
100
|
+
userName: options.userName,
|
|
101
|
+
dashboardUrl: options.dashboardUrl,
|
|
102
|
+
});
|
|
103
|
+
return sendEmail({
|
|
104
|
+
to: options.to,
|
|
105
|
+
from: options.branding.supportEmail.replace("support@", "noreply@"),
|
|
106
|
+
subject: `Welcome to ${options.branding.appName}! 🎉`,
|
|
107
|
+
html,
|
|
108
|
+
apiKey: options.apiKey,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AppBranding } from "../branding";
|
|
2
|
+
interface MagicLinkEmailProps {
|
|
3
|
+
magicLink: string;
|
|
4
|
+
branding: AppBranding;
|
|
5
|
+
userEmail?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const MagicLinkEmail: ({ magicLink, branding, userEmail, }: MagicLinkEmailProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export default MagicLinkEmail;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Body, Button, Container, Head, Heading, Html, Link, Preview, Section, Text, Hr, } from "@react-email/components";
|
|
3
|
+
export const MagicLinkEmail = ({ magicLink, branding, userEmail, }) => {
|
|
4
|
+
const previewText = `Sign in to ${branding.appName}`;
|
|
5
|
+
return (_jsxs(Html, { children: [_jsx(Head, {}), _jsx(Preview, { children: previewText }), _jsx(Body, { style: main, children: _jsxs(Container, { style: container, children: [_jsx(Section, { style: {
|
|
6
|
+
...heroHeader,
|
|
7
|
+
backgroundImage: branding.logoGradient,
|
|
8
|
+
backgroundColor: branding.primaryColor,
|
|
9
|
+
}, children: _jsx(Heading, { style: brandHeading, children: branding.appName.includes("Cards") ? (_jsxs(_Fragment, { children: ["Cards", _jsx("span", { style: { fontWeight: 400 }, children: "Gone" }), _jsx("span", { style: { fontStyle: "italic" }, children: "Crazy" })] })) : branding.appName.includes("Retirement") ? (_jsxs(_Fragment, { children: ["Route", _jsx("span", { style: { opacity: 0.75 }, children: "My" }), "Retirement"] })) : branding.appName.includes("Sharp") ? (_jsxs(_Fragment, { children: ["Fake", _jsx("span", { style: { fontWeight: 300 }, children: "Sharp" })] })) : (branding.appName) }) }), _jsxs(Section, { style: contentContainer, children: [_jsx(Heading, { style: mainHeading, children: "Sign in to your account" }), _jsxs(Text, { style: paragraph, children: ["We received a request to sign in to your account", userEmail ? _jsxs("span", { style: { color: "#334155", fontWeight: 600 }, children: [" (", userEmail, ")"] }) : "", "."] }), _jsx(Text, { style: paragraph, children: "Click the button below to authenticate clearly and securely." }), _jsx(Section, { style: buttonContainer, children: _jsx(Button, { style: {
|
|
10
|
+
...button,
|
|
11
|
+
backgroundColor: branding.primaryColor,
|
|
12
|
+
boxShadow: `0 0 20px ${branding.primaryColor}40`, // 25% opacity glow
|
|
13
|
+
}, href: magicLink, children: "Sign In" }) }), _jsx(Text, { style: paragraphSmall, children: "Or paste this link into your browser:" }), _jsx(Text, { style: linkText, children: magicLink }), _jsx(Hr, { style: divider }), _jsx(Text, { style: paragraphMuted, children: "If you didn't request this, you can safely ignore this email." }), _jsxs(Section, { style: footer, children: [_jsx(Text, { style: footerText, children: branding.footerText || `© ${branding.appName}` }), _jsx(Link, { href: branding.websiteUrl, style: footerLink, children: branding.websiteUrl.replace("https://", "") })] })] })] }) })] }));
|
|
14
|
+
};
|
|
15
|
+
// Styles
|
|
16
|
+
const main = {
|
|
17
|
+
backgroundColor: "#f8fafc", // Slate-50
|
|
18
|
+
fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
19
|
+
padding: "40px 0",
|
|
20
|
+
};
|
|
21
|
+
const container = {
|
|
22
|
+
backgroundColor: "#ffffff", // White
|
|
23
|
+
margin: "0 auto",
|
|
24
|
+
padding: "0",
|
|
25
|
+
maxWidth: "520px",
|
|
26
|
+
borderRadius: "16px",
|
|
27
|
+
border: "1px solid #e2e8f0", // Slate-200
|
|
28
|
+
overflow: "hidden",
|
|
29
|
+
boxShadow: "0 10px 15px -3px rgba(0, 0, 0, 0.05), 0 4px 6px -2px rgba(0, 0, 0, 0.03)", // Soft shadow
|
|
30
|
+
};
|
|
31
|
+
const heroHeader = {
|
|
32
|
+
padding: "48px 0",
|
|
33
|
+
textAlign: "center",
|
|
34
|
+
};
|
|
35
|
+
const contentContainer = {
|
|
36
|
+
padding: "48px",
|
|
37
|
+
};
|
|
38
|
+
const brandHeading = {
|
|
39
|
+
margin: "0",
|
|
40
|
+
fontSize: "28px",
|
|
41
|
+
fontWeight: "800",
|
|
42
|
+
color: "#ffffff",
|
|
43
|
+
letterSpacing: "-0.5px",
|
|
44
|
+
textShadow: "0 2px 4px rgba(0,0,0,0.1)",
|
|
45
|
+
};
|
|
46
|
+
const mainHeading = {
|
|
47
|
+
color: "#0f172a", // Slate-900
|
|
48
|
+
fontSize: "30px",
|
|
49
|
+
fontWeight: "700",
|
|
50
|
+
margin: "0 0 24px",
|
|
51
|
+
letterSpacing: "-0.5px",
|
|
52
|
+
lineHeight: "38px",
|
|
53
|
+
};
|
|
54
|
+
const paragraph = {
|
|
55
|
+
color: "#334155", // Slate-700
|
|
56
|
+
fontSize: "16px",
|
|
57
|
+
lineHeight: "26px",
|
|
58
|
+
margin: "0 0 16px",
|
|
59
|
+
};
|
|
60
|
+
const buttonContainer = {
|
|
61
|
+
margin: "32px 0",
|
|
62
|
+
};
|
|
63
|
+
const button = {
|
|
64
|
+
color: "#ffffff",
|
|
65
|
+
fontSize: "15px",
|
|
66
|
+
fontWeight: "600",
|
|
67
|
+
textDecoration: "none",
|
|
68
|
+
textAlign: "center",
|
|
69
|
+
display: "inline-block",
|
|
70
|
+
padding: "14px 32px",
|
|
71
|
+
borderRadius: "8px",
|
|
72
|
+
};
|
|
73
|
+
const paragraphSmall = {
|
|
74
|
+
color: "#64748b", // Slate-500
|
|
75
|
+
fontSize: "13px",
|
|
76
|
+
lineHeight: "20px",
|
|
77
|
+
margin: "0 0 8px",
|
|
78
|
+
};
|
|
79
|
+
const linkText = {
|
|
80
|
+
color: "#94a3b8", // Slate-400
|
|
81
|
+
fontSize: "12px",
|
|
82
|
+
lineHeight: "18px",
|
|
83
|
+
wordBreak: "break-all",
|
|
84
|
+
margin: "0 0 24px",
|
|
85
|
+
};
|
|
86
|
+
const divider = {
|
|
87
|
+
borderColor: "#e2e8f0", // Slate-200
|
|
88
|
+
margin: "32px 0",
|
|
89
|
+
};
|
|
90
|
+
const paragraphMuted = {
|
|
91
|
+
color: "#94a3b8",
|
|
92
|
+
fontSize: "13px",
|
|
93
|
+
lineHeight: "20px",
|
|
94
|
+
margin: "0 0 24px",
|
|
95
|
+
};
|
|
96
|
+
const footer = {
|
|
97
|
+
textAlign: "center",
|
|
98
|
+
};
|
|
99
|
+
const footerText = {
|
|
100
|
+
color: "#94a3b8", // Slate-400
|
|
101
|
+
fontSize: "12px",
|
|
102
|
+
margin: "0 0 8px",
|
|
103
|
+
};
|
|
104
|
+
const footerLink = {
|
|
105
|
+
color: "#64748b", // Slate-500
|
|
106
|
+
fontSize: "12px",
|
|
107
|
+
textDecoration: "underline",
|
|
108
|
+
};
|
|
109
|
+
export default MagicLinkEmail;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AppBranding } from "../branding";
|
|
2
|
+
interface PasswordResetEmailProps {
|
|
3
|
+
resetLink: string;
|
|
4
|
+
branding: AppBranding;
|
|
5
|
+
userEmail?: string;
|
|
6
|
+
expiresIn?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const PasswordResetEmail: ({ resetLink, branding, userEmail, expiresIn, }: PasswordResetEmailProps) => import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export default PasswordResetEmail;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Body, Button, Container, Head, Heading, Html, Link, Preview, Section, Text, Hr, } from "@react-email/components";
|
|
3
|
+
export const PasswordResetEmail = ({ resetLink, branding, userEmail, expiresIn = "1 hour", }) => {
|
|
4
|
+
const previewText = `Reset your ${branding.appName} password`;
|
|
5
|
+
return (_jsxs(Html, { children: [_jsx(Head, {}), _jsx(Preview, { children: previewText }), _jsx(Body, { style: main, children: _jsxs(Container, { style: container, children: [_jsx(Section, { style: {
|
|
6
|
+
...heroHeader,
|
|
7
|
+
backgroundImage: branding.logoGradient,
|
|
8
|
+
backgroundColor: branding.primaryColor,
|
|
9
|
+
}, children: _jsx(Heading, { style: brandHeading, children: branding.appName.includes("Cards") ? (_jsxs(_Fragment, { children: ["Cards", _jsx("span", { style: { fontWeight: 400 }, children: "Gone" }), _jsx("span", { style: { fontStyle: "italic" }, children: "Crazy" })] })) : branding.appName.includes("Retirement") ? (_jsxs(_Fragment, { children: ["Route", _jsx("span", { style: { opacity: 0.75 }, children: "My" }), "Retirement"] })) : branding.appName.includes("Sharp") ? (_jsxs(_Fragment, { children: ["Fake", _jsx("span", { style: { fontWeight: 300 }, children: "Sharp" })] })) : (branding.appName) }) }), _jsxs(Section, { style: contentContainer, children: [_jsx(Heading, { style: mainHeading, children: "Reset your password" }), _jsxs(Text, { style: paragraph, children: ["We received a request to update the password for your account", userEmail ? _jsxs("span", { style: { color: "#334155", fontWeight: 600 }, children: [" (", userEmail, ")"] }) : "", "."] }), _jsx(Text, { style: paragraph, children: "To create a new password, click the button below." }), _jsx(Section, { style: buttonContainer, children: _jsx(Button, { style: {
|
|
10
|
+
...button,
|
|
11
|
+
backgroundColor: branding.primaryColor,
|
|
12
|
+
boxShadow: `0 0 20px ${branding.primaryColor}40`,
|
|
13
|
+
}, href: resetLink, children: "Reset Password" }) }), _jsx(Text, { style: paragraphSmall, children: "Or paste this link into your browser:" }), _jsx(Text, { style: linkText, children: resetLink }), _jsxs(Text, { style: { ...paragraphMuted, margin: "16px 0 0" }, children: ["This link will expire in ", _jsx("span", { style: { color: "#d4d4d8" }, children: expiresIn }), "."] }), _jsx(Hr, { style: divider }), _jsxs(Section, { style: warningBox, children: [_jsx(Text, { style: warningTitle, children: "SECURITY NOTICE" }), _jsx(Text, { style: warningText, children: "If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged." })] }), _jsxs(Section, { style: footer, children: [_jsx(Text, { style: footerText, children: branding.footerText || `© ${branding.appName}` }), _jsx(Link, { href: branding.websiteUrl, style: footerLink, children: branding.websiteUrl.replace("https://", "") })] })] })] }) })] }));
|
|
14
|
+
};
|
|
15
|
+
// Styles
|
|
16
|
+
const main = {
|
|
17
|
+
backgroundColor: "#f8fafc", // Slate-50
|
|
18
|
+
fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
19
|
+
padding: "40px 0",
|
|
20
|
+
};
|
|
21
|
+
const container = {
|
|
22
|
+
backgroundColor: "#ffffff", // White
|
|
23
|
+
margin: "0 auto",
|
|
24
|
+
padding: "0",
|
|
25
|
+
maxWidth: "520px",
|
|
26
|
+
borderRadius: "16px",
|
|
27
|
+
border: "1px solid #e2e8f0", // Slate-200
|
|
28
|
+
overflow: "hidden",
|
|
29
|
+
boxShadow: "0 10px 15px -3px rgba(0, 0, 0, 0.05), 0 4px 6px -2px rgba(0, 0, 0, 0.03)",
|
|
30
|
+
};
|
|
31
|
+
const heroHeader = {
|
|
32
|
+
padding: "48px 0",
|
|
33
|
+
textAlign: "center",
|
|
34
|
+
};
|
|
35
|
+
const contentContainer = {
|
|
36
|
+
padding: "48px",
|
|
37
|
+
};
|
|
38
|
+
const brandHeading = {
|
|
39
|
+
margin: "0",
|
|
40
|
+
fontSize: "28px",
|
|
41
|
+
fontWeight: "800",
|
|
42
|
+
color: "#ffffff",
|
|
43
|
+
letterSpacing: "-0.5px",
|
|
44
|
+
textShadow: "0 2px 4px rgba(0,0,0,0.1)",
|
|
45
|
+
};
|
|
46
|
+
const mainHeading = {
|
|
47
|
+
color: "#0f172a", // Slate-900
|
|
48
|
+
fontSize: "30px",
|
|
49
|
+
fontWeight: "700",
|
|
50
|
+
margin: "0 0 24px",
|
|
51
|
+
letterSpacing: "-0.5px",
|
|
52
|
+
lineHeight: "38px",
|
|
53
|
+
};
|
|
54
|
+
const paragraph = {
|
|
55
|
+
color: "#334155", // Slate-700
|
|
56
|
+
fontSize: "16px",
|
|
57
|
+
lineHeight: "26px",
|
|
58
|
+
margin: "0 0 16px",
|
|
59
|
+
};
|
|
60
|
+
const buttonContainer = {
|
|
61
|
+
margin: "32px 0",
|
|
62
|
+
};
|
|
63
|
+
const button = {
|
|
64
|
+
color: "#ffffff",
|
|
65
|
+
fontSize: "15px",
|
|
66
|
+
fontWeight: "600",
|
|
67
|
+
textDecoration: "none",
|
|
68
|
+
textAlign: "center",
|
|
69
|
+
display: "inline-block",
|
|
70
|
+
padding: "14px 32px",
|
|
71
|
+
borderRadius: "8px",
|
|
72
|
+
};
|
|
73
|
+
const paragraphSmall = {
|
|
74
|
+
color: "#64748b", // Slate-500
|
|
75
|
+
fontSize: "13px",
|
|
76
|
+
lineHeight: "20px",
|
|
77
|
+
margin: "0 0 8px",
|
|
78
|
+
};
|
|
79
|
+
const linkText = {
|
|
80
|
+
color: "#94a3b8", // Slate-400
|
|
81
|
+
fontSize: "12px",
|
|
82
|
+
lineHeight: "18px",
|
|
83
|
+
wordBreak: "break-all",
|
|
84
|
+
margin: "0 0 24px",
|
|
85
|
+
};
|
|
86
|
+
const divider = {
|
|
87
|
+
borderColor: "#e2e8f0", // Slate-200
|
|
88
|
+
margin: "32px 0",
|
|
89
|
+
};
|
|
90
|
+
const paragraphMuted = {
|
|
91
|
+
color: "#64748b", // Slate-500
|
|
92
|
+
fontSize: "13px",
|
|
93
|
+
lineHeight: "20px",
|
|
94
|
+
};
|
|
95
|
+
const warningBox = {
|
|
96
|
+
padding: "16px",
|
|
97
|
+
backgroundColor: "#fffbeb", // Amber-50
|
|
98
|
+
border: "1px solid #fcd34d", // Amber-300
|
|
99
|
+
borderRadius: "8px",
|
|
100
|
+
margin: "0 0 24px",
|
|
101
|
+
};
|
|
102
|
+
const warningTitle = {
|
|
103
|
+
color: "#b45309", // Amber-700
|
|
104
|
+
fontSize: "11px",
|
|
105
|
+
fontWeight: "700",
|
|
106
|
+
letterSpacing: "1px",
|
|
107
|
+
margin: "0 0 8px",
|
|
108
|
+
};
|
|
109
|
+
const warningText = {
|
|
110
|
+
color: "#92400e", // Amber-800
|
|
111
|
+
fontSize: "13px",
|
|
112
|
+
lineHeight: "20px",
|
|
113
|
+
margin: "0",
|
|
114
|
+
};
|
|
115
|
+
const footer = {
|
|
116
|
+
textAlign: "center",
|
|
117
|
+
};
|
|
118
|
+
const footerText = {
|
|
119
|
+
color: "#94a3b8", // Slate-400
|
|
120
|
+
fontSize: "12px",
|
|
121
|
+
margin: "0 0 8px",
|
|
122
|
+
};
|
|
123
|
+
const footerLink = {
|
|
124
|
+
color: "#64748b", // Slate-500
|
|
125
|
+
fontSize: "12px",
|
|
126
|
+
textDecoration: "underline",
|
|
127
|
+
};
|
|
128
|
+
export default PasswordResetEmail;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AppBranding } from "../branding";
|
|
2
|
+
interface WelcomeEmailProps {
|
|
3
|
+
branding: AppBranding;
|
|
4
|
+
userName?: string;
|
|
5
|
+
dashboardUrl?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const WelcomeEmail: ({ branding, userName, dashboardUrl, }: WelcomeEmailProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export default WelcomeEmail;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Body, Button, Container, Head, Heading, Html, Link, Preview, Section, Text, Hr, } from "@react-email/components";
|
|
3
|
+
export const WelcomeEmail = ({ branding, userName, dashboardUrl, }) => {
|
|
4
|
+
const previewText = `Welcome to ${branding.appName}!`;
|
|
5
|
+
const ctaUrl = dashboardUrl || branding.websiteUrl;
|
|
6
|
+
return (_jsxs(Html, { children: [_jsx(Head, {}), _jsx(Preview, { children: previewText }), _jsx(Body, { style: main, children: _jsxs(Container, { style: container, children: [_jsx(Section, { style: {
|
|
7
|
+
...heroHeader,
|
|
8
|
+
backgroundImage: branding.logoGradient,
|
|
9
|
+
backgroundColor: branding.primaryColor,
|
|
10
|
+
}, children: _jsx(Heading, { style: brandHeading, children: branding.appName.includes("Cards") ? (_jsxs(_Fragment, { children: ["Cards", _jsx("span", { style: { fontWeight: 400 }, children: "Gone" }), _jsx("span", { style: { fontStyle: "italic" }, children: "Crazy" })] })) : branding.appName.includes("Retirement") ? (_jsxs(_Fragment, { children: ["Route", _jsx("span", { style: { opacity: 0.75 }, children: "My" }), "Retirement"] })) : branding.appName.includes("Sharp") ? (_jsxs(_Fragment, { children: ["Fake", _jsx("span", { style: { fontWeight: 300 }, children: "Sharp" })] })) : (branding.appName) }) }), _jsxs(Section, { style: contentContainer, children: [_jsx(Heading, { style: mainHeading, children: "Welcome aboard!" }), _jsx(Text, { style: paragraph, children: userName ? `Hi ${userName},` : "Hello," }), _jsxs(Text, { style: paragraph, children: ["Thanks for joining ", _jsx("span", { style: { color: "#334155", fontWeight: 700 }, children: branding.appName }), ". We're thrilled to have you with us."] }), _jsxs(Section, { style: featuresContainer, children: [_jsx(Text, { style: featureHeading, children: "GETTING STARTED" }), _jsxs(Section, { style: featureRow, children: [_jsx("div", { style: { ...bullet, backgroundColor: branding.primaryColor } }), _jsx(Text, { style: featureText, children: "Explore your new command center" })] }), _jsxs(Section, { style: featureRow, children: [_jsx("div", { style: { ...bullet, backgroundColor: branding.primaryColor } }), _jsx(Text, { style: featureText, children: "Customize your preferences and settings" })] }), _jsxs(Section, { style: featureRow, children: [_jsx("div", { style: { ...bullet, backgroundColor: branding.primaryColor } }), _jsx(Text, { style: featureText, children: "Start your first project or plan" })] })] }), _jsx(Section, { style: buttonContainer, children: _jsx(Button, { style: {
|
|
11
|
+
...button,
|
|
12
|
+
backgroundColor: branding.primaryColor,
|
|
13
|
+
boxShadow: `0 0 20px ${branding.primaryColor}40`,
|
|
14
|
+
}, href: ctaUrl, children: "Get Started" }) }), _jsx(Hr, { style: divider }), _jsxs(Text, { style: paragraphMuted, children: ["Need help? Reply to this email or contact ", _jsx(Link, { href: `mailto:${branding.supportEmail}`, style: link, children: branding.supportEmail }), "."] }), _jsxs(Section, { style: footer, children: [_jsx(Text, { style: footerText, children: branding.footerText || `© ${branding.appName}` }), _jsx(Link, { href: branding.websiteUrl, style: footerLink, children: branding.websiteUrl.replace("https://", "") })] })] })] }) })] }));
|
|
15
|
+
};
|
|
16
|
+
// Styles
|
|
17
|
+
const main = {
|
|
18
|
+
backgroundColor: "#f8fafc", // Slate-50
|
|
19
|
+
fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
20
|
+
padding: "40px 0",
|
|
21
|
+
};
|
|
22
|
+
const container = {
|
|
23
|
+
backgroundColor: "#ffffff", // White
|
|
24
|
+
margin: "0 auto",
|
|
25
|
+
padding: "0",
|
|
26
|
+
maxWidth: "520px",
|
|
27
|
+
borderRadius: "16px",
|
|
28
|
+
border: "1px solid #e2e8f0", // Slate-200
|
|
29
|
+
overflow: "hidden",
|
|
30
|
+
boxShadow: "0 10px 15px -3px rgba(0, 0, 0, 0.05), 0 4px 6px -2px rgba(0, 0, 0, 0.03)",
|
|
31
|
+
};
|
|
32
|
+
const heroHeader = {
|
|
33
|
+
padding: "48px 0",
|
|
34
|
+
textAlign: "center",
|
|
35
|
+
};
|
|
36
|
+
const contentContainer = {
|
|
37
|
+
padding: "48px",
|
|
38
|
+
};
|
|
39
|
+
const brandHeading = {
|
|
40
|
+
margin: "0",
|
|
41
|
+
fontSize: "28px",
|
|
42
|
+
fontWeight: "800",
|
|
43
|
+
color: "#ffffff",
|
|
44
|
+
letterSpacing: "-0.5px",
|
|
45
|
+
textShadow: "0 2px 4px rgba(0,0,0,0.1)",
|
|
46
|
+
};
|
|
47
|
+
const mainHeading = {
|
|
48
|
+
color: "#0f172a", // Slate-900
|
|
49
|
+
fontSize: "30px",
|
|
50
|
+
fontWeight: "700",
|
|
51
|
+
margin: "0 0 24px",
|
|
52
|
+
letterSpacing: "-0.5px",
|
|
53
|
+
lineHeight: "38px",
|
|
54
|
+
};
|
|
55
|
+
const paragraph = {
|
|
56
|
+
color: "#334155", // Slate-700
|
|
57
|
+
fontSize: "16px",
|
|
58
|
+
lineHeight: "26px",
|
|
59
|
+
margin: "0 0 16px",
|
|
60
|
+
};
|
|
61
|
+
const featuresContainer = {
|
|
62
|
+
margin: "32px 0 32px",
|
|
63
|
+
padding: "24px",
|
|
64
|
+
backgroundColor: "#f1f5f9", // Slate-100
|
|
65
|
+
borderRadius: "12px",
|
|
66
|
+
border: "1px solid #e2e8f0", // Slate-200
|
|
67
|
+
};
|
|
68
|
+
const featureHeading = {
|
|
69
|
+
color: "#64748b", // Slate-500
|
|
70
|
+
fontSize: "11px",
|
|
71
|
+
fontWeight: "700",
|
|
72
|
+
letterSpacing: "1px",
|
|
73
|
+
margin: "0 0 16px",
|
|
74
|
+
textTransform: "uppercase",
|
|
75
|
+
};
|
|
76
|
+
const featureRow = {
|
|
77
|
+
marginBottom: "12px",
|
|
78
|
+
};
|
|
79
|
+
const bullet = {
|
|
80
|
+
width: "8px",
|
|
81
|
+
height: "8px",
|
|
82
|
+
borderRadius: "50%",
|
|
83
|
+
display: "inline-block",
|
|
84
|
+
marginRight: "12px",
|
|
85
|
+
};
|
|
86
|
+
const featureText = {
|
|
87
|
+
color: "#475569", // Slate-600
|
|
88
|
+
fontSize: "14px",
|
|
89
|
+
fontWeight: "600",
|
|
90
|
+
display: "inline-block",
|
|
91
|
+
margin: "0",
|
|
92
|
+
};
|
|
93
|
+
const buttonContainer = {
|
|
94
|
+
margin: "32px 0",
|
|
95
|
+
};
|
|
96
|
+
const button = {
|
|
97
|
+
color: "#ffffff",
|
|
98
|
+
fontSize: "15px",
|
|
99
|
+
fontWeight: "600",
|
|
100
|
+
textDecoration: "none",
|
|
101
|
+
textAlign: "center",
|
|
102
|
+
display: "inline-block",
|
|
103
|
+
padding: "14px 32px",
|
|
104
|
+
borderRadius: "8px",
|
|
105
|
+
};
|
|
106
|
+
const divider = {
|
|
107
|
+
borderColor: "#e2e8f0", // Slate-200
|
|
108
|
+
margin: "32px 0",
|
|
109
|
+
};
|
|
110
|
+
const paragraphMuted = {
|
|
111
|
+
color: "#94a3b8", // Slate-400
|
|
112
|
+
fontSize: "13px",
|
|
113
|
+
lineHeight: "20px",
|
|
114
|
+
margin: "0 0 8px",
|
|
115
|
+
};
|
|
116
|
+
const link = {
|
|
117
|
+
color: "#64748b", // Slate-500
|
|
118
|
+
textDecoration: "underline",
|
|
119
|
+
};
|
|
120
|
+
const footer = {
|
|
121
|
+
marginTop: "24px",
|
|
122
|
+
textAlign: "center",
|
|
123
|
+
};
|
|
124
|
+
const footerText = {
|
|
125
|
+
color: "#94a3b8", // Slate-400
|
|
126
|
+
fontSize: "12px",
|
|
127
|
+
margin: "0 0 8px",
|
|
128
|
+
};
|
|
129
|
+
const footerLink = {
|
|
130
|
+
color: "#64748b", // Slate-500
|
|
131
|
+
fontSize: "12px",
|
|
132
|
+
textDecoration: "underline",
|
|
133
|
+
};
|
|
134
|
+
export default WelcomeEmail;
|