brookmind-emails 0.1.7
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/.prettierrc +7 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/modelfy/config.d.ts +7 -0
- package/dist/modelfy/config.d.ts.map +1 -0
- package/dist/modelfy/config.js +6 -0
- package/dist/modelfy/index.d.ts +14 -0
- package/dist/modelfy/index.d.ts.map +1 -0
- package/dist/modelfy/index.js +33 -0
- package/dist/modelfy/templates/GrantCreditsEmail.d.ts +10 -0
- package/dist/modelfy/templates/GrantCreditsEmail.d.ts.map +1 -0
- package/dist/modelfy/templates/GrantCreditsEmail.js +101 -0
- package/dist/modelfy/templates/OtpEmail.d.ts +8 -0
- package/dist/modelfy/templates/OtpEmail.d.ts.map +1 -0
- package/dist/modelfy/templates/OtpEmail.js +66 -0
- package/dist/modelfy/templates/PromoSubscriptionEmail.d.ts +11 -0
- package/dist/modelfy/templates/PromoSubscriptionEmail.d.ts.map +1 -0
- package/dist/modelfy/templates/PromoSubscriptionEmail.js +83 -0
- package/dist/modelfy/templates/index.d.ts +4 -0
- package/dist/modelfy/templates/index.d.ts.map +1 -0
- package/dist/modelfy/templates/index.js +3 -0
- package/dist/shared/components/Button.d.ts +7 -0
- package/dist/shared/components/Button.d.ts.map +1 -0
- package/dist/shared/components/Button.js +27 -0
- package/dist/shared/components/Footer.d.ts +8 -0
- package/dist/shared/components/Footer.d.ts.map +1 -0
- package/dist/shared/components/Footer.js +28 -0
- package/dist/shared/components/Header.d.ts +7 -0
- package/dist/shared/components/Header.d.ts.map +1 -0
- package/dist/shared/components/Header.js +40 -0
- package/dist/shared/components/index.d.ts +4 -0
- package/dist/shared/components/index.d.ts.map +1 -0
- package/dist/shared/components/index.js +3 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +1 -0
- package/package.json +40 -0
- package/src/index.ts +5 -0
- package/src/modelfy/config.ts +6 -0
- package/src/modelfy/index.ts +57 -0
- package/src/modelfy/templates/GrantCreditsEmail.tsx +205 -0
- package/src/modelfy/templates/OtpEmail.tsx +128 -0
- package/src/modelfy/templates/PromoSubscriptionEmail.tsx +189 -0
- package/src/modelfy/templates/index.ts +3 -0
- package/src/shared/components/Button.tsx +39 -0
- package/src/shared/components/Footer.tsx +63 -0
- package/src/shared/components/Header.tsx +56 -0
- package/src/shared/components/index.ts +3 -0
- package/src/shared/index.ts +1 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { render } from "@react-email/render";
|
|
2
|
+
import { OtpEmail, type OtpEmailProps } from "./templates/OtpEmail.js";
|
|
3
|
+
import {
|
|
4
|
+
PromoSubscriptionEmail,
|
|
5
|
+
type PromoSubscriptionEmailProps,
|
|
6
|
+
} from "./templates/PromoSubscriptionEmail.js";
|
|
7
|
+
import { GrantCreditsEmail, type GrantCreditsEmailProps } from "./templates/GrantCreditsEmail.js";
|
|
8
|
+
|
|
9
|
+
// Re-export types
|
|
10
|
+
export type { OtpEmailProps, PromoSubscriptionEmailProps, GrantCreditsEmailProps };
|
|
11
|
+
|
|
12
|
+
// Re-export components for preview
|
|
13
|
+
export { OtpEmail, PromoSubscriptionEmail, GrantCreditsEmail };
|
|
14
|
+
|
|
15
|
+
// Render functions
|
|
16
|
+
export interface RenderResult {
|
|
17
|
+
subject: string;
|
|
18
|
+
html: string;
|
|
19
|
+
text: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function renderOtpEmail(props: OtpEmailProps): Promise<RenderResult> {
|
|
23
|
+
const html = await render(OtpEmail(props));
|
|
24
|
+
const text = await render(OtpEmail(props), { plainText: true });
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
subject: "Your Modelfy access code",
|
|
28
|
+
html,
|
|
29
|
+
text,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function renderPromoSubscriptionEmail(
|
|
34
|
+
props: PromoSubscriptionEmailProps
|
|
35
|
+
): Promise<RenderResult> {
|
|
36
|
+
const html = await render(PromoSubscriptionEmail(props));
|
|
37
|
+
const text = await render(PromoSubscriptionEmail(props), { plainText: true });
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
subject: `You've received a ${props.durationDays}-day ${props.planName} subscription!`,
|
|
41
|
+
html,
|
|
42
|
+
text,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function renderGrantCreditsEmail(
|
|
47
|
+
props: GrantCreditsEmailProps
|
|
48
|
+
): Promise<RenderResult> {
|
|
49
|
+
const html = await render(GrantCreditsEmail(props));
|
|
50
|
+
const text = await render(GrantCreditsEmail(props), { plainText: true });
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
subject: `You've received ${(props.credits ?? 0).toLocaleString()} credits on Modelfy!`,
|
|
54
|
+
html,
|
|
55
|
+
text,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Html,
|
|
3
|
+
Head,
|
|
4
|
+
Body,
|
|
5
|
+
Container,
|
|
6
|
+
Section,
|
|
7
|
+
Text,
|
|
8
|
+
Link,
|
|
9
|
+
Preview,
|
|
10
|
+
Font,
|
|
11
|
+
} from "@react-email/components";
|
|
12
|
+
import { Header, Footer, Button } from "../../shared/components/index.js";
|
|
13
|
+
import { modelfyConfig } from "../config.js";
|
|
14
|
+
|
|
15
|
+
export interface GrantCreditsEmailProps {
|
|
16
|
+
email: string;
|
|
17
|
+
credits?: number;
|
|
18
|
+
durationDays: number;
|
|
19
|
+
expiresAt: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const styles = {
|
|
24
|
+
body: {
|
|
25
|
+
margin: 0,
|
|
26
|
+
padding: 0,
|
|
27
|
+
backgroundColor: "#F8FAFC",
|
|
28
|
+
fontFamily: "'Outfit', Arial, sans-serif",
|
|
29
|
+
},
|
|
30
|
+
container: {
|
|
31
|
+
maxWidth: "560px",
|
|
32
|
+
margin: "32px auto",
|
|
33
|
+
backgroundColor: "#ffffff",
|
|
34
|
+
border: "1px solid #E2E8F0",
|
|
35
|
+
borderRadius: "16px",
|
|
36
|
+
boxShadow: "0 24px 48px rgba(15, 23, 42, 0.08)",
|
|
37
|
+
overflow: "hidden",
|
|
38
|
+
},
|
|
39
|
+
content: {
|
|
40
|
+
padding: "32px 40px 24px",
|
|
41
|
+
},
|
|
42
|
+
heading: {
|
|
43
|
+
margin: "0 0 16px",
|
|
44
|
+
fontSize: "24px",
|
|
45
|
+
fontWeight: 700,
|
|
46
|
+
color: "#0F172A",
|
|
47
|
+
textAlign: "center" as const,
|
|
48
|
+
},
|
|
49
|
+
paragraph: {
|
|
50
|
+
margin: "0 0 24px",
|
|
51
|
+
fontSize: "16px",
|
|
52
|
+
lineHeight: "1.6",
|
|
53
|
+
color: "rgba(15, 23, 42, 0.72)",
|
|
54
|
+
},
|
|
55
|
+
creditBox: {
|
|
56
|
+
backgroundColor: "rgba(34, 197, 94, 0.08)",
|
|
57
|
+
border: "1px solid rgba(34, 197, 94, 0.24)",
|
|
58
|
+
borderRadius: "12px",
|
|
59
|
+
padding: "24px",
|
|
60
|
+
marginBottom: "24px",
|
|
61
|
+
textAlign: "center" as const,
|
|
62
|
+
},
|
|
63
|
+
creditAmount: {
|
|
64
|
+
fontSize: "48px",
|
|
65
|
+
fontWeight: 700,
|
|
66
|
+
color: "#16A34A",
|
|
67
|
+
margin: "0 0 8px",
|
|
68
|
+
},
|
|
69
|
+
creditLabel: {
|
|
70
|
+
fontSize: "16px",
|
|
71
|
+
color: "rgba(15, 23, 42, 0.6)",
|
|
72
|
+
margin: 0,
|
|
73
|
+
},
|
|
74
|
+
detailsBox: {
|
|
75
|
+
backgroundColor: "#F8FAFC",
|
|
76
|
+
borderRadius: "8px",
|
|
77
|
+
padding: "16px",
|
|
78
|
+
marginBottom: "24px",
|
|
79
|
+
},
|
|
80
|
+
detailRow: {
|
|
81
|
+
display: "flex" as const,
|
|
82
|
+
justifyContent: "space-between" as const,
|
|
83
|
+
marginBottom: "8px",
|
|
84
|
+
},
|
|
85
|
+
detailLabel: {
|
|
86
|
+
fontSize: "14px",
|
|
87
|
+
color: "rgba(15, 23, 42, 0.6)",
|
|
88
|
+
margin: 0,
|
|
89
|
+
},
|
|
90
|
+
detailValue: {
|
|
91
|
+
fontSize: "14px",
|
|
92
|
+
fontWeight: 600,
|
|
93
|
+
color: "#0F172A",
|
|
94
|
+
margin: 0,
|
|
95
|
+
},
|
|
96
|
+
buttonContainer: {
|
|
97
|
+
textAlign: "center" as const,
|
|
98
|
+
marginBottom: "24px",
|
|
99
|
+
},
|
|
100
|
+
smallText: {
|
|
101
|
+
margin: "0 0 12px",
|
|
102
|
+
fontSize: "14px",
|
|
103
|
+
lineHeight: "1.6",
|
|
104
|
+
color: "rgba(15, 23, 42, 0.6)",
|
|
105
|
+
},
|
|
106
|
+
link: {
|
|
107
|
+
color: "#6366F1",
|
|
108
|
+
textDecoration: "none",
|
|
109
|
+
fontWeight: 600,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export function GrantCreditsEmail({
|
|
114
|
+
email,
|
|
115
|
+
credits,
|
|
116
|
+
durationDays,
|
|
117
|
+
expiresAt,
|
|
118
|
+
description,
|
|
119
|
+
}: GrantCreditsEmailProps) {
|
|
120
|
+
const safeCredits = Number.isFinite(credits) ? Number(credits) : 0;
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Html lang="en">
|
|
124
|
+
<Head>
|
|
125
|
+
<Font
|
|
126
|
+
fontFamily="Outfit"
|
|
127
|
+
fallbackFontFamily="Arial"
|
|
128
|
+
webFont={{
|
|
129
|
+
url: "https://fonts.gstatic.com/s/outfit/v11/QGYyz_MVcBeNP4NjuGObqx1XmO1I4TC1C4G-EiAou6Y.woff2",
|
|
130
|
+
format: "woff2",
|
|
131
|
+
}}
|
|
132
|
+
fontWeight={400}
|
|
133
|
+
fontStyle="normal"
|
|
134
|
+
/>
|
|
135
|
+
</Head>
|
|
136
|
+
<Preview>
|
|
137
|
+
You've received {safeCredits.toLocaleString()} credits on Modelfy!
|
|
138
|
+
</Preview>
|
|
139
|
+
<Body style={styles.body}>
|
|
140
|
+
<Container style={styles.container}>
|
|
141
|
+
<Header logoUrl={modelfyConfig.logoUrl} logoAlt="Modelfy" subtitle="Credits added" />
|
|
142
|
+
<Section style={styles.content}>
|
|
143
|
+
<Text style={styles.heading}>Credits Added to Your Account!</Text>
|
|
144
|
+
<Text style={styles.paragraph}>
|
|
145
|
+
Hi <strong>{email}</strong>, you've received bonus credits to use on Modelfy.
|
|
146
|
+
</Text>
|
|
147
|
+
|
|
148
|
+
<div style={styles.creditBox}>
|
|
149
|
+
<Text style={styles.creditAmount}>+{safeCredits.toLocaleString()}</Text>
|
|
150
|
+
<Text style={styles.creditLabel}>credits</Text>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div style={styles.detailsBox}>
|
|
154
|
+
<table width="100%" cellPadding={0} cellSpacing={0}>
|
|
155
|
+
<tr>
|
|
156
|
+
<td style={{ paddingBottom: "8px" }}>
|
|
157
|
+
<Text style={styles.detailLabel}>Valid for</Text>
|
|
158
|
+
</td>
|
|
159
|
+
<td style={{ paddingBottom: "8px", textAlign: "right" }}>
|
|
160
|
+
<Text style={styles.detailValue}>{durationDays} days</Text>
|
|
161
|
+
</td>
|
|
162
|
+
</tr>
|
|
163
|
+
<tr>
|
|
164
|
+
<td>
|
|
165
|
+
<Text style={styles.detailLabel}>Expires</Text>
|
|
166
|
+
</td>
|
|
167
|
+
<td style={{ textAlign: "right" }}>
|
|
168
|
+
<Text style={styles.detailValue}>{expiresAt}</Text>
|
|
169
|
+
</td>
|
|
170
|
+
</tr>
|
|
171
|
+
</table>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
{description && (
|
|
175
|
+
<Text style={styles.paragraph}>
|
|
176
|
+
<em>"{description}"</em>
|
|
177
|
+
</Text>
|
|
178
|
+
)}
|
|
179
|
+
|
|
180
|
+
<div style={styles.buttonContainer}>
|
|
181
|
+
<Button href={modelfyConfig.appUrl}>Start Creating</Button>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<Text style={styles.smallText}>
|
|
185
|
+
These credits will be used before your regular credits and expire on {expiresAt}.
|
|
186
|
+
</Text>
|
|
187
|
+
<Text style={styles.smallText}>
|
|
188
|
+
Questions? Contact us at{" "}
|
|
189
|
+
<Link href={`mailto:${modelfyConfig.supportEmail}`} style={styles.link}>
|
|
190
|
+
{modelfyConfig.supportEmail}
|
|
191
|
+
</Link>
|
|
192
|
+
</Text>
|
|
193
|
+
</Section>
|
|
194
|
+
<Footer
|
|
195
|
+
companyName={modelfyConfig.companyName}
|
|
196
|
+
message="You received this email because credits were added to your Modelfy account."
|
|
197
|
+
supportEmail={modelfyConfig.supportEmail}
|
|
198
|
+
/>
|
|
199
|
+
</Container>
|
|
200
|
+
</Body>
|
|
201
|
+
</Html>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export default GrantCreditsEmail;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Html,
|
|
3
|
+
Head,
|
|
4
|
+
Body,
|
|
5
|
+
Container,
|
|
6
|
+
Section,
|
|
7
|
+
Text,
|
|
8
|
+
Link,
|
|
9
|
+
Preview,
|
|
10
|
+
Font,
|
|
11
|
+
} from "@react-email/components";
|
|
12
|
+
import { Header, Footer } from "../../shared/components/index.js";
|
|
13
|
+
import { modelfyConfig } from "../config.js";
|
|
14
|
+
|
|
15
|
+
export interface OtpEmailProps {
|
|
16
|
+
otp: string;
|
|
17
|
+
email: string;
|
|
18
|
+
expirationMinutes?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const styles = {
|
|
22
|
+
body: {
|
|
23
|
+
margin: 0,
|
|
24
|
+
padding: 0,
|
|
25
|
+
backgroundColor: "#F8FAFC",
|
|
26
|
+
fontFamily: "'Outfit', Arial, sans-serif",
|
|
27
|
+
},
|
|
28
|
+
container: {
|
|
29
|
+
maxWidth: "560px",
|
|
30
|
+
margin: "32px auto",
|
|
31
|
+
backgroundColor: "#ffffff",
|
|
32
|
+
border: "1px solid #E2E8F0",
|
|
33
|
+
borderRadius: "16px",
|
|
34
|
+
boxShadow: "0 24px 48px rgba(15, 23, 42, 0.08)",
|
|
35
|
+
overflow: "hidden",
|
|
36
|
+
},
|
|
37
|
+
content: {
|
|
38
|
+
padding: "32px 40px 24px",
|
|
39
|
+
},
|
|
40
|
+
paragraph: {
|
|
41
|
+
margin: "16px 0 24px",
|
|
42
|
+
fontSize: "16px",
|
|
43
|
+
lineHeight: "1.6",
|
|
44
|
+
color: "rgba(15, 23, 42, 0.72)",
|
|
45
|
+
},
|
|
46
|
+
otpContainer: {
|
|
47
|
+
display: "inline-block",
|
|
48
|
+
backgroundColor: "rgba(99, 102, 241, 0.08)",
|
|
49
|
+
border: "1px solid rgba(99, 102, 241, 0.24)",
|
|
50
|
+
borderRadius: "12px",
|
|
51
|
+
padding: "20px 32px",
|
|
52
|
+
marginBottom: "24px",
|
|
53
|
+
},
|
|
54
|
+
otp: {
|
|
55
|
+
fontSize: "32px",
|
|
56
|
+
letterSpacing: "8px",
|
|
57
|
+
fontWeight: 700,
|
|
58
|
+
color: "#0F172A",
|
|
59
|
+
margin: 0,
|
|
60
|
+
},
|
|
61
|
+
smallText: {
|
|
62
|
+
margin: "0 0 12px",
|
|
63
|
+
fontSize: "15px",
|
|
64
|
+
lineHeight: "1.6",
|
|
65
|
+
color: "rgba(15, 23, 42, 0.72)",
|
|
66
|
+
},
|
|
67
|
+
link: {
|
|
68
|
+
color: "#0F172A",
|
|
69
|
+
textDecoration: "none",
|
|
70
|
+
fontWeight: 600,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export function OtpEmail({ otp, email, expirationMinutes = 10 }: OtpEmailProps) {
|
|
75
|
+
const sanitizedOtp = (typeof otp === "string" ? otp : String(otp ?? ""))
|
|
76
|
+
.replace(/\s+/g, "")
|
|
77
|
+
.trim();
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Html lang="en">
|
|
81
|
+
<Head>
|
|
82
|
+
<Font
|
|
83
|
+
fontFamily="Outfit"
|
|
84
|
+
fallbackFontFamily="Arial"
|
|
85
|
+
webFont={{
|
|
86
|
+
url: "https://fonts.gstatic.com/s/outfit/v11/QGYyz_MVcBeNP4NjuGObqx1XmO1I4TC1C4G-EiAou6Y.woff2",
|
|
87
|
+
format: "woff2",
|
|
88
|
+
}}
|
|
89
|
+
fontWeight={400}
|
|
90
|
+
fontStyle="normal"
|
|
91
|
+
/>
|
|
92
|
+
</Head>
|
|
93
|
+
<Preview>Your Modelfy access code: {sanitizedOtp}</Preview>
|
|
94
|
+
<Body style={styles.body}>
|
|
95
|
+
<Container style={styles.container}>
|
|
96
|
+
<Header logoUrl={modelfyConfig.logoUrl} logoAlt="Modelfy" subtitle="Access code" />
|
|
97
|
+
<Section style={styles.content}>
|
|
98
|
+
<Text style={styles.paragraph}>
|
|
99
|
+
Hi <strong>{email}</strong>, use the code below to sign in. It will stay valid for the
|
|
100
|
+
next <strong>{expirationMinutes} minutes</strong>.
|
|
101
|
+
</Text>
|
|
102
|
+
<div style={styles.otpContainer}>
|
|
103
|
+
<Text style={styles.otp}>{sanitizedOtp}</Text>
|
|
104
|
+
</div>
|
|
105
|
+
<Text style={styles.smallText}>
|
|
106
|
+
If you didn't request this code, you can safely ignore this email. No one else can
|
|
107
|
+
access your account without it.
|
|
108
|
+
</Text>
|
|
109
|
+
<Text style={styles.smallText}>
|
|
110
|
+
Need a hand? Reach us anytime at{" "}
|
|
111
|
+
<Link href={`mailto:${modelfyConfig.supportEmail}`} style={styles.link}>
|
|
112
|
+
{modelfyConfig.supportEmail}
|
|
113
|
+
</Link>
|
|
114
|
+
.
|
|
115
|
+
</Text>
|
|
116
|
+
</Section>
|
|
117
|
+
<Footer
|
|
118
|
+
companyName={modelfyConfig.companyName}
|
|
119
|
+
message="You received this email because someone attempted to sign in to Modelfy with this address."
|
|
120
|
+
supportEmail={modelfyConfig.supportEmail}
|
|
121
|
+
/>
|
|
122
|
+
</Container>
|
|
123
|
+
</Body>
|
|
124
|
+
</Html>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export default OtpEmail;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Html,
|
|
3
|
+
Head,
|
|
4
|
+
Body,
|
|
5
|
+
Container,
|
|
6
|
+
Section,
|
|
7
|
+
Text,
|
|
8
|
+
Link,
|
|
9
|
+
Preview,
|
|
10
|
+
Font,
|
|
11
|
+
} from "@react-email/components";
|
|
12
|
+
import { Header, Footer, Button } from "../../shared/components/index.js";
|
|
13
|
+
import { modelfyConfig } from "../config.js";
|
|
14
|
+
|
|
15
|
+
export interface PromoSubscriptionEmailProps {
|
|
16
|
+
email: string;
|
|
17
|
+
planName: string;
|
|
18
|
+
durationDays: number;
|
|
19
|
+
credits?: number;
|
|
20
|
+
expiresAt: string;
|
|
21
|
+
notes?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const styles = {
|
|
25
|
+
body: {
|
|
26
|
+
margin: 0,
|
|
27
|
+
padding: 0,
|
|
28
|
+
backgroundColor: "#F8FAFC",
|
|
29
|
+
fontFamily: "'Outfit', Arial, sans-serif",
|
|
30
|
+
},
|
|
31
|
+
container: {
|
|
32
|
+
maxWidth: "560px",
|
|
33
|
+
margin: "32px auto",
|
|
34
|
+
backgroundColor: "#ffffff",
|
|
35
|
+
border: "1px solid #E2E8F0",
|
|
36
|
+
borderRadius: "16px",
|
|
37
|
+
boxShadow: "0 24px 48px rgba(15, 23, 42, 0.08)",
|
|
38
|
+
overflow: "hidden",
|
|
39
|
+
},
|
|
40
|
+
content: {
|
|
41
|
+
padding: "32px 40px 24px",
|
|
42
|
+
},
|
|
43
|
+
heading: {
|
|
44
|
+
margin: "0 0 16px",
|
|
45
|
+
fontSize: "24px",
|
|
46
|
+
fontWeight: 700,
|
|
47
|
+
color: "#0F172A",
|
|
48
|
+
textAlign: "center" as const,
|
|
49
|
+
},
|
|
50
|
+
paragraph: {
|
|
51
|
+
margin: "0 0 24px",
|
|
52
|
+
fontSize: "16px",
|
|
53
|
+
lineHeight: "1.6",
|
|
54
|
+
color: "rgba(15, 23, 42, 0.72)",
|
|
55
|
+
},
|
|
56
|
+
highlightBox: {
|
|
57
|
+
backgroundColor: "rgba(99, 102, 241, 0.08)",
|
|
58
|
+
border: "1px solid rgba(99, 102, 241, 0.24)",
|
|
59
|
+
borderRadius: "12px",
|
|
60
|
+
padding: "24px",
|
|
61
|
+
marginBottom: "24px",
|
|
62
|
+
},
|
|
63
|
+
highlightRow: {
|
|
64
|
+
display: "flex" as const,
|
|
65
|
+
justifyContent: "space-between" as const,
|
|
66
|
+
marginBottom: "12px",
|
|
67
|
+
},
|
|
68
|
+
highlightLabel: {
|
|
69
|
+
fontSize: "14px",
|
|
70
|
+
color: "rgba(15, 23, 42, 0.6)",
|
|
71
|
+
margin: 0,
|
|
72
|
+
},
|
|
73
|
+
highlightValue: {
|
|
74
|
+
fontSize: "16px",
|
|
75
|
+
fontWeight: 600,
|
|
76
|
+
color: "#0F172A",
|
|
77
|
+
margin: 0,
|
|
78
|
+
},
|
|
79
|
+
buttonContainer: {
|
|
80
|
+
textAlign: "center" as const,
|
|
81
|
+
marginBottom: "24px",
|
|
82
|
+
},
|
|
83
|
+
smallText: {
|
|
84
|
+
margin: "0 0 12px",
|
|
85
|
+
fontSize: "14px",
|
|
86
|
+
lineHeight: "1.6",
|
|
87
|
+
color: "rgba(15, 23, 42, 0.6)",
|
|
88
|
+
},
|
|
89
|
+
link: {
|
|
90
|
+
color: "#6366F1",
|
|
91
|
+
textDecoration: "none",
|
|
92
|
+
fontWeight: 600,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export function PromoSubscriptionEmail({
|
|
97
|
+
email,
|
|
98
|
+
planName,
|
|
99
|
+
durationDays,
|
|
100
|
+
credits,
|
|
101
|
+
expiresAt,
|
|
102
|
+
notes,
|
|
103
|
+
}: PromoSubscriptionEmailProps) {
|
|
104
|
+
const safeCredits = Number.isFinite(credits) ? Number(credits) : 0;
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<Html lang="en">
|
|
108
|
+
<Head>
|
|
109
|
+
<Font
|
|
110
|
+
fontFamily="Outfit"
|
|
111
|
+
fallbackFontFamily="Arial"
|
|
112
|
+
webFont={{
|
|
113
|
+
url: "https://fonts.gstatic.com/s/outfit/v11/QGYyz_MVcBeNP4NjuGObqx1XmO1I4TC1C4G-EiAou6Y.woff2",
|
|
114
|
+
format: "woff2",
|
|
115
|
+
}}
|
|
116
|
+
fontWeight={400}
|
|
117
|
+
fontStyle="normal"
|
|
118
|
+
/>
|
|
119
|
+
</Head>
|
|
120
|
+
<Preview>
|
|
121
|
+
{`You've received a ${durationDays}-day ${planName} subscription!`}
|
|
122
|
+
</Preview>
|
|
123
|
+
<Body style={styles.body}>
|
|
124
|
+
<Container style={styles.container}>
|
|
125
|
+
<Header logoUrl={modelfyConfig.logoUrl} logoAlt="Modelfy" subtitle="Gift for you" />
|
|
126
|
+
<Section style={styles.content}>
|
|
127
|
+
<Text style={styles.heading}>You've Got a Gift!</Text>
|
|
128
|
+
<Text style={styles.paragraph}>
|
|
129
|
+
Hi <strong>{email}</strong>, great news! You've been granted a promotional{" "}
|
|
130
|
+
<strong>{planName}</strong> subscription.
|
|
131
|
+
</Text>
|
|
132
|
+
|
|
133
|
+
<div style={styles.highlightBox}>
|
|
134
|
+
<table width="100%" cellPadding={0} cellSpacing={0}>
|
|
135
|
+
<tr>
|
|
136
|
+
<td style={{ paddingBottom: "12px" }}>
|
|
137
|
+
<Text style={styles.highlightLabel}>Plan</Text>
|
|
138
|
+
<Text style={styles.highlightValue}>{planName}</Text>
|
|
139
|
+
</td>
|
|
140
|
+
<td style={{ paddingBottom: "12px", textAlign: "right" }}>
|
|
141
|
+
<Text style={styles.highlightLabel}>Duration</Text>
|
|
142
|
+
<Text style={styles.highlightValue}>{durationDays} days</Text>
|
|
143
|
+
</td>
|
|
144
|
+
</tr>
|
|
145
|
+
<tr>
|
|
146
|
+
<td>
|
|
147
|
+
<Text style={styles.highlightLabel}>Credits Included</Text>
|
|
148
|
+
<Text style={styles.highlightValue}>{safeCredits.toLocaleString()}</Text>
|
|
149
|
+
</td>
|
|
150
|
+
<td style={{ textAlign: "right" }}>
|
|
151
|
+
<Text style={styles.highlightLabel}>Expires</Text>
|
|
152
|
+
<Text style={styles.highlightValue}>{expiresAt}</Text>
|
|
153
|
+
</td>
|
|
154
|
+
</tr>
|
|
155
|
+
</table>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
{notes && (
|
|
159
|
+
<Text style={styles.paragraph}>
|
|
160
|
+
<em>"{notes}"</em>
|
|
161
|
+
</Text>
|
|
162
|
+
)}
|
|
163
|
+
|
|
164
|
+
<div style={styles.buttonContainer}>
|
|
165
|
+
<Button href={modelfyConfig.appUrl}>Open Modelfy</Button>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<Text style={styles.smallText}>
|
|
169
|
+
This promotional subscription will not auto-renew. Enjoy your free access!
|
|
170
|
+
</Text>
|
|
171
|
+
<Text style={styles.smallText}>
|
|
172
|
+
Questions? Contact us at{" "}
|
|
173
|
+
<Link href={`mailto:${modelfyConfig.supportEmail}`} style={styles.link}>
|
|
174
|
+
{modelfyConfig.supportEmail}
|
|
175
|
+
</Link>
|
|
176
|
+
</Text>
|
|
177
|
+
</Section>
|
|
178
|
+
<Footer
|
|
179
|
+
companyName={modelfyConfig.companyName}
|
|
180
|
+
message="You received this email because you were granted a promotional subscription."
|
|
181
|
+
supportEmail={modelfyConfig.supportEmail}
|
|
182
|
+
/>
|
|
183
|
+
</Container>
|
|
184
|
+
</Body>
|
|
185
|
+
</Html>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export default PromoSubscriptionEmail;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Button as ReactEmailButton } from "@react-email/components";
|
|
2
|
+
|
|
3
|
+
export interface ButtonProps {
|
|
4
|
+
href: string;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
variant?: "primary" | "secondary";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const baseStyles = {
|
|
10
|
+
display: "inline-block",
|
|
11
|
+
padding: "14px 28px",
|
|
12
|
+
fontSize: "16px",
|
|
13
|
+
fontWeight: 600,
|
|
14
|
+
textDecoration: "none",
|
|
15
|
+
borderRadius: "8px",
|
|
16
|
+
textAlign: "center" as const,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const variants = {
|
|
20
|
+
primary: {
|
|
21
|
+
...baseStyles,
|
|
22
|
+
backgroundColor: "#18181B",
|
|
23
|
+
color: "#ffffff",
|
|
24
|
+
},
|
|
25
|
+
secondary: {
|
|
26
|
+
...baseStyles,
|
|
27
|
+
backgroundColor: "transparent",
|
|
28
|
+
color: "#18181B",
|
|
29
|
+
border: "2px solid #18181B",
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function Button({ href, children, variant = "primary" }: ButtonProps) {
|
|
34
|
+
return (
|
|
35
|
+
<ReactEmailButton href={href} style={variants[variant]}>
|
|
36
|
+
{children}
|
|
37
|
+
</ReactEmailButton>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Section, Text, Link } from "@react-email/components";
|
|
2
|
+
|
|
3
|
+
export interface FooterProps {
|
|
4
|
+
companyName: string;
|
|
5
|
+
year?: number;
|
|
6
|
+
message?: string;
|
|
7
|
+
supportEmail?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const styles = {
|
|
11
|
+
footer: {
|
|
12
|
+
padding: "20px 40px",
|
|
13
|
+
backgroundColor: "#F1F5F9",
|
|
14
|
+
borderTop: "1px solid rgba(148, 163, 184, 0.2)",
|
|
15
|
+
},
|
|
16
|
+
text: {
|
|
17
|
+
margin: 0,
|
|
18
|
+
fontSize: "13px",
|
|
19
|
+
lineHeight: "1.6",
|
|
20
|
+
color: "rgba(15, 23, 42, 0.72)",
|
|
21
|
+
},
|
|
22
|
+
noReplyText: {
|
|
23
|
+
margin: "12px 0 0",
|
|
24
|
+
fontSize: "12px",
|
|
25
|
+
lineHeight: "1.5",
|
|
26
|
+
color: "rgba(15, 23, 42, 0.5)",
|
|
27
|
+
},
|
|
28
|
+
link: {
|
|
29
|
+
color: "#6366F1",
|
|
30
|
+
textDecoration: "none",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function Footer({
|
|
35
|
+
companyName,
|
|
36
|
+
year = new Date().getFullYear(),
|
|
37
|
+
message,
|
|
38
|
+
supportEmail,
|
|
39
|
+
}: FooterProps) {
|
|
40
|
+
return (
|
|
41
|
+
<Section style={styles.footer}>
|
|
42
|
+
<Text style={styles.text}>
|
|
43
|
+
© {year} {companyName}. All rights reserved.
|
|
44
|
+
{message && (
|
|
45
|
+
<>
|
|
46
|
+
<br />
|
|
47
|
+
{message}
|
|
48
|
+
</>
|
|
49
|
+
)}
|
|
50
|
+
</Text>
|
|
51
|
+
{supportEmail && (
|
|
52
|
+
<Text style={styles.noReplyText}>
|
|
53
|
+
This is an automated message. Please do not reply to this email. For any questions,
|
|
54
|
+
contact us at{" "}
|
|
55
|
+
<Link href={`mailto:${supportEmail}`} style={styles.link}>
|
|
56
|
+
{supportEmail}
|
|
57
|
+
</Link>
|
|
58
|
+
.
|
|
59
|
+
</Text>
|
|
60
|
+
)}
|
|
61
|
+
</Section>
|
|
62
|
+
);
|
|
63
|
+
}
|