@tracked/emails 0.1.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.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +211 -0
  3. package/dist/emails/bodyweight-goal-reached.d.ts +14 -0
  4. package/dist/emails/bodyweight-goal-reached.d.ts.map +1 -0
  5. package/dist/emails/bodyweight-goal-reached.js +177 -0
  6. package/dist/emails/bodyweight-goal-reached.js.map +1 -0
  7. package/dist/emails/client-accepted-invitation.d.ts +10 -0
  8. package/dist/emails/client-accepted-invitation.d.ts.map +1 -0
  9. package/dist/emails/client-accepted-invitation.js +99 -0
  10. package/dist/emails/client-accepted-invitation.js.map +1 -0
  11. package/dist/emails/coach-invite.d.ts +10 -0
  12. package/dist/emails/coach-invite.d.ts.map +1 -0
  13. package/dist/emails/coach-invite.js +126 -0
  14. package/dist/emails/coach-invite.js.map +1 -0
  15. package/dist/emails/coach-removed-client.d.ts +8 -0
  16. package/dist/emails/coach-removed-client.d.ts.map +1 -0
  17. package/dist/emails/coach-removed-client.js +80 -0
  18. package/dist/emails/coach-removed-client.js.map +1 -0
  19. package/dist/emails/direct-message.d.ts +11 -0
  20. package/dist/emails/direct-message.d.ts.map +1 -0
  21. package/dist/emails/direct-message.js +103 -0
  22. package/dist/emails/direct-message.js.map +1 -0
  23. package/dist/emails/feature-discovery.d.ts +11 -0
  24. package/dist/emails/feature-discovery.d.ts.map +1 -0
  25. package/dist/emails/feature-discovery.js +121 -0
  26. package/dist/emails/feature-discovery.js.map +1 -0
  27. package/dist/emails/first-workout-assigned.d.ts +10 -0
  28. package/dist/emails/first-workout-assigned.d.ts.map +1 -0
  29. package/dist/emails/first-workout-assigned.js +98 -0
  30. package/dist/emails/first-workout-assigned.js.map +1 -0
  31. package/dist/emails/first-workout-completed.d.ts +11 -0
  32. package/dist/emails/first-workout-completed.d.ts.map +1 -0
  33. package/dist/emails/first-workout-completed.js +129 -0
  34. package/dist/emails/first-workout-completed.js.map +1 -0
  35. package/dist/emails/index.d.ts +7 -0
  36. package/dist/emails/index.d.ts.map +1 -0
  37. package/dist/emails/index.js +7 -0
  38. package/dist/emails/index.js.map +1 -0
  39. package/dist/emails/new-follower.d.ts +11 -0
  40. package/dist/emails/new-follower.d.ts.map +1 -0
  41. package/dist/emails/new-follower.js +98 -0
  42. package/dist/emails/new-follower.js.map +1 -0
  43. package/dist/emails/subscription-canceled.d.ts +10 -0
  44. package/dist/emails/subscription-canceled.d.ts.map +1 -0
  45. package/dist/emails/subscription-canceled.js +131 -0
  46. package/dist/emails/subscription-canceled.js.map +1 -0
  47. package/dist/emails/support-email.d.ts +8 -0
  48. package/dist/emails/support-email.d.ts.map +1 -0
  49. package/dist/emails/support-email.js +40 -0
  50. package/dist/emails/support-email.js.map +1 -0
  51. package/dist/emails/team-invite.d.ts +11 -0
  52. package/dist/emails/team-invite.d.ts.map +1 -0
  53. package/dist/emails/team-invite.js +100 -0
  54. package/dist/emails/team-invite.js.map +1 -0
  55. package/dist/emails/team-member-removed-email.d.ts +8 -0
  56. package/dist/emails/team-member-removed-email.d.ts.map +1 -0
  57. package/dist/emails/team-member-removed-email.js +97 -0
  58. package/dist/emails/team-member-removed-email.js.map +1 -0
  59. package/dist/emails/tracked-magic-link-activate.d.ts +7 -0
  60. package/dist/emails/tracked-magic-link-activate.d.ts.map +1 -0
  61. package/dist/emails/tracked-magic-link-activate.js +93 -0
  62. package/dist/emails/tracked-magic-link-activate.js.map +1 -0
  63. package/dist/emails/tracked-magic-link.d.ts +7 -0
  64. package/dist/emails/tracked-magic-link.d.ts.map +1 -0
  65. package/dist/emails/tracked-magic-link.js +101 -0
  66. package/dist/emails/tracked-magic-link.js.map +1 -0
  67. package/dist/emails/week-one-checkin.d.ts +10 -0
  68. package/dist/emails/week-one-checkin.d.ts.map +1 -0
  69. package/dist/emails/week-one-checkin.js +141 -0
  70. package/dist/emails/week-one-checkin.js.map +1 -0
  71. package/dist/emails/welcome.d.ts +8 -0
  72. package/dist/emails/welcome.d.ts.map +1 -0
  73. package/dist/emails/welcome.js +146 -0
  74. package/dist/emails/welcome.js.map +1 -0
  75. package/dist/index.d.ts +22 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +24 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/utils/email-validation.d.ts +48 -0
  80. package/dist/utils/email-validation.d.ts.map +1 -0
  81. package/dist/utils/email-validation.js +72 -0
  82. package/dist/utils/email-validation.js.map +1 -0
  83. package/dist/utils/index.d.ts +8 -0
  84. package/dist/utils/index.d.ts.map +1 -0
  85. package/dist/utils/index.js +8 -0
  86. package/dist/utils/index.js.map +1 -0
  87. package/dist/utils/username-validation.d.ts +54 -0
  88. package/dist/utils/username-validation.d.ts.map +1 -0
  89. package/dist/utils/username-validation.js +76 -0
  90. package/dist/utils/username-validation.js.map +1 -0
  91. package/package.json +78 -0
  92. package/src/emails/bodyweight-goal-reached.tsx +396 -0
  93. package/src/emails/client-accepted-invitation.tsx +258 -0
  94. package/src/emails/coach-invite.tsx +270 -0
  95. package/src/emails/coach-removed-client.tsx +212 -0
  96. package/src/emails/direct-message.tsx +249 -0
  97. package/src/emails/feature-discovery.tsx +289 -0
  98. package/src/emails/first-workout-assigned.tsx +255 -0
  99. package/src/emails/first-workout-completed.tsx +312 -0
  100. package/src/emails/index.tsx +6 -0
  101. package/src/emails/new-follower.tsx +260 -0
  102. package/src/emails/subscription-canceled.tsx +311 -0
  103. package/src/emails/support-email.tsx +80 -0
  104. package/src/emails/team-invite.tsx +262 -0
  105. package/src/emails/team-member-removed-email.tsx +240 -0
  106. package/src/emails/tracked-magic-link-activate.tsx +252 -0
  107. package/src/emails/tracked-magic-link.tsx +264 -0
  108. package/src/emails/week-one-checkin.tsx +353 -0
  109. package/src/emails/welcome.tsx +341 -0
  110. package/src/index.ts +57 -0
  111. package/src/utils/email-validation.test.ts +78 -0
  112. package/src/utils/email-validation.ts +80 -0
  113. package/src/utils/index.ts +13 -0
  114. package/src/utils/username-validation.test.ts +118 -0
  115. package/src/utils/username-validation.ts +89 -0
  116. package/static/tracked-logo.png +0 -0
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Username Validation Utilities
3
+ *
4
+ * Handles validation for usernames to determine if they're user-chosen or
5
+ * auto-generated UUIDs. Auto-generated usernames should not be displayed in emails.
6
+ */
7
+ /**
8
+ * Check if username is a generated UUID (not user-chosen)
9
+ *
10
+ * The database schema uses UUIDv7 as the default for usernames when users
11
+ * don't provide one. Format: 01944f9e-8e64-7a78-9e1e-3daba7b13e9f
12
+ *
13
+ * UUIDv7/v4 pattern: 8-4-4-4-12 hex characters separated by hyphens
14
+ *
15
+ * @param username - The username to check
16
+ * @returns true if the username is an auto-generated UUID
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * isAnonymousUsername("01944f9e-8e64-7a78-9e1e-3daba7b13e9f") // true
21
+ * isAnonymousUsername("john_doe") // false
22
+ * isAnonymousUsername(null) // true
23
+ * ```
24
+ */
25
+ export function isAnonymousUsername(username) {
26
+ if (!username) {
27
+ return true;
28
+ }
29
+ // UUIDv7/v4 pattern: 8-4-4-4-12 hex characters
30
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
31
+ return uuidRegex.test(username);
32
+ }
33
+ /**
34
+ * Get safe display name for emails with intelligent fallback
35
+ *
36
+ * Tries to find a user-friendly display name by checking multiple sources
37
+ * in order of preference:
38
+ * 1. Real username (non-UUID)
39
+ * 2. Given name / First name
40
+ * 3. Full name
41
+ * 4. Generic fallback ("there")
42
+ *
43
+ * This ensures we never show auto-generated UUIDs in emails.
44
+ *
45
+ * @param username - The user's username (may be UUID)
46
+ * @param givenName - The user's first/given name
47
+ * @param name - The user's full name
48
+ * @param fallback - Default fallback if no good name is found (default: "there")
49
+ * @returns A safe, user-friendly display name
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * getSafeDisplayName("john_doe", "John", "John Doe") // "john_doe"
54
+ * getSafeDisplayName("01944f9e...", "John", "John Doe") // "John"
55
+ * getSafeDisplayName("01944f9e...", null, "John Doe") // "John Doe"
56
+ * getSafeDisplayName("01944f9e...", null, null) // "there"
57
+ * getSafeDisplayName(null, null, null, "friend") // "friend"
58
+ * ```
59
+ */
60
+ export function getSafeDisplayName(username, givenName, name, fallback = "there") {
61
+ // Try real username first (skip if it's a UUID)
62
+ if (username && !isAnonymousUsername(username)) {
63
+ return username;
64
+ }
65
+ // Try given name
66
+ if (givenName && givenName.trim().length > 0) {
67
+ return givenName;
68
+ }
69
+ // Try full name
70
+ if (name && name.trim().length > 0) {
71
+ return name;
72
+ }
73
+ // Use fallback
74
+ return fallback;
75
+ }
76
+ //# sourceMappingURL=username-validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"username-validation.js","sourceRoot":"","sources":["../../src/utils/username-validation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAmC;IAEnC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC;IAED,+CAA+C;IAC/C,MAAM,SAAS,GACb,iEAAiE,CAAC;IACpE,OAAO,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAmC,EACnC,SAAoC,EACpC,IAA+B,EAC/B,WAAmB,OAAO;IAE1B,gDAAgD;IAChD,IAAI,QAAQ,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/C,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,iBAAiB;IACjB,IAAI,SAAS,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7C,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,gBAAgB;IAChB,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,eAAe;IACf,OAAO,QAAQ,CAAC;AAClB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@tracked/emails",
3
+ "version": "0.1.0",
4
+ "description": "Email templates for Tracked Training Platform",
5
+ "author": "Tracked Training Platform Inc.",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "./emails/*": "./dist/emails/*.js"
17
+ },
18
+ "files": [
19
+ "dist/**",
20
+ "src/**",
21
+ "static/**",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "scripts": {
26
+ "build": "rm -rf ./dist && tsc",
27
+ "dev:email": "email dev",
28
+ "export": "email export",
29
+ "typecheck": "tsc --noEmit",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "test:coverage": "vitest run --coverage",
33
+ "lint": "eslint src --ext .ts,.tsx",
34
+ "lint:fix": "eslint src --ext .ts,.tsx --fix",
35
+ "format": "prettier --check .",
36
+ "format:fix": "prettier --write .",
37
+ "clean": "rm -rf dist node_modules .turbo"
38
+ },
39
+ "dependencies": {
40
+ "@react-email/components": "^0.5.7",
41
+ "react": "19.0.0",
42
+ "react-dom": "19.0.0",
43
+ "react-email": "^4.3.2"
44
+ },
45
+ "devDependencies": {
46
+ "@react-email/preview-server": "latest",
47
+ "@types/node": "^22.10.2",
48
+ "@types/react": "^18.3.2",
49
+ "@types/react-dom": "^18.3.0",
50
+ "@typescript-eslint/eslint-plugin": "^8.20.0",
51
+ "@typescript-eslint/parser": "^8.20.0",
52
+ "@vitest/coverage-v8": "^2.1.8",
53
+ "dotenv-cli": "^7.4.2",
54
+ "eslint": "^9.17.0",
55
+ "eslint-plugin-react": "^7.37.2",
56
+ "eslint-plugin-react-hooks": "^5.1.0",
57
+ "prettier": "^3.4.2",
58
+ "typescript": "^5.8.3",
59
+ "vitest": "^2.1.8"
60
+ },
61
+ "keywords": [
62
+ "email",
63
+ "react-email",
64
+ "templates",
65
+ "tracked"
66
+ ],
67
+ "repository": {
68
+ "type": "git",
69
+ "url": "https://github.com/tracked/emails"
70
+ },
71
+ "publishConfig": {
72
+ "access": "public"
73
+ },
74
+ "engines": {
75
+ "node": ">=18.0.0"
76
+ },
77
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
78
+ }
@@ -0,0 +1,396 @@
1
+ import React from "react";
2
+ import {
3
+ Body,
4
+ Column,
5
+ Container,
6
+ Head,
7
+ Hr,
8
+ Html,
9
+ Img,
10
+ Link,
11
+ Preview,
12
+ Row,
13
+ Section,
14
+ Text,
15
+ } from "@react-email/components";
16
+
17
+ interface BodyweightGoalReachedEmailProps {
18
+ userName: string;
19
+ goalType: "gain" | "loss" | "maintain";
20
+ startWeight: number;
21
+ currentWeight: number;
22
+ goalWeight: number;
23
+ weightUnit: "kg" | "lbs";
24
+ timeToGoal?: string;
25
+ progressUrl: string;
26
+ websiteUrl: string;
27
+ }
28
+
29
+ const baseUrl = "https://tracked.gg/android-chrome-192x192.png";
30
+
31
+ export const BodyweightGoalReachedEmail = ({
32
+ userName,
33
+ goalType,
34
+ startWeight,
35
+ currentWeight,
36
+ goalWeight,
37
+ weightUnit,
38
+ timeToGoal,
39
+ progressUrl,
40
+ websiteUrl = "https://tracked.gg",
41
+ }: BodyweightGoalReachedEmailProps) => {
42
+ const weightChange = Math.abs(currentWeight - startWeight);
43
+ const changeDirection = currentWeight > startWeight ? "gained" : "lost";
44
+
45
+ const getGoalMessage = () => {
46
+ switch (goalType) {
47
+ case "gain":
48
+ return "You've reached your weight gain goal!";
49
+ case "loss":
50
+ return "You've reached your weight loss goal!";
51
+ case "maintain":
52
+ return "You've successfully maintained your goal weight!";
53
+ default:
54
+ return "You've reached your goal!";
55
+ }
56
+ };
57
+
58
+ return (
59
+ <Html>
60
+ <Head>
61
+ <meta name="color-scheme" content="light only" />
62
+ <meta name="supported-color-schemes" content="light only" />
63
+ </Head>
64
+ <Preview>Congratulations! You've reached your bodyweight goal on Tracked</Preview>
65
+ <Body style={main}>
66
+ <Container style={container}>
67
+ <Section style={box}>
68
+ <Row style={{ marginBottom: "8px" }}>
69
+ <Column style={{ width: "auto", verticalAlign: "middle" }}>
70
+ <Img src={`${baseUrl}`} width="28" height="28" alt="Tracked" />
71
+ </Column>
72
+ <Column
73
+ style={{
74
+ width: "auto",
75
+ verticalAlign: "middle",
76
+ paddingLeft: "4px",
77
+ }}
78
+ >
79
+ <Text
80
+ style={{
81
+ fontSize: "28px",
82
+ fontWeight: "900",
83
+ fontFamily:
84
+ "Raleway, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
85
+ color: "#020617",
86
+ margin: "0",
87
+ lineHeight: "32px",
88
+ letterSpacing: "0.5px",
89
+ }}
90
+ >
91
+ TRACKED
92
+ </Text>
93
+ </Column>
94
+ </Row>
95
+ <Hr style={hr} />
96
+
97
+ <Section style={{ textAlign: "center" as const, margin: "24px 0" }}>
98
+ <Text style={{ fontSize: "64px", margin: "0" }}>🎉</Text>
99
+ </Section>
100
+
101
+ <Text style={heading}>Goal Achieved!</Text>
102
+ <Text style={celebrationText}>{getGoalMessage()}</Text>
103
+
104
+ <Section style={statsBox}>
105
+ <Text style={statsHeading}>Your Achievement:</Text>
106
+ <Row>
107
+ <Column>
108
+ <Text style={statsLabel}>Starting Weight</Text>
109
+ <Text style={statsValue}>
110
+ {startWeight} {weightUnit}
111
+ </Text>
112
+ </Column>
113
+ <Column>
114
+ <Text style={statsLabel}>Current Weight</Text>
115
+ <Text style={statsValue}>
116
+ {currentWeight} {weightUnit}
117
+ </Text>
118
+ </Column>
119
+ <Column>
120
+ <Text style={statsLabel}>Goal Weight</Text>
121
+ <Text style={statsValue}>
122
+ {goalWeight} {weightUnit}
123
+ </Text>
124
+ </Column>
125
+ </Row>
126
+ <Section style={changeBox}>
127
+ <Text style={changeText}>
128
+ You've {changeDirection} {weightChange.toFixed(1)} {weightUnit}
129
+ {timeToGoal && ` in ${timeToGoal}`}!
130
+ </Text>
131
+ </Section>
132
+ </Section>
133
+
134
+ <Text style={paragraph}>
135
+ Congratulations, {userName}! This is a huge accomplishment that took
136
+ dedication, consistency, and hard work. You set a goal and achieved
137
+ it - that's something to be proud of!
138
+ </Text>
139
+
140
+ <div
141
+ style={{
142
+ marginTop: "24px",
143
+ marginBottom: "24px",
144
+ textAlign: "left" as const,
145
+ }}
146
+ >
147
+ <a
148
+ href={progressUrl}
149
+ style={{
150
+ backgroundColor: "#0f172a",
151
+ borderRadius: "8px",
152
+ fontSize: "16px",
153
+ fontWeight: "bold",
154
+ textDecoration: "none",
155
+ padding: "12px 32px",
156
+ display: "inline-block",
157
+ }}
158
+ >
159
+ <span style={{ color: "#ffffff", textDecoration: "none" }}>
160
+ View Your Progress
161
+ </span>
162
+ </a>
163
+ </div>
164
+
165
+ <Section style={nextStepsBox}>
166
+ <Text style={nextStepsHeading}>What's Next?</Text>
167
+ <ul style={nextStepsList}>
168
+ <li style={nextStepItem}>
169
+ Maintain your progress with consistent training
170
+ </li>
171
+ <li style={nextStepItem}>
172
+ Set a new goal to continue your journey
173
+ </li>
174
+ <li style={nextStepItem}>
175
+ Share your success with the Tracked community
176
+ </li>
177
+ <li style={nextStepItem}>
178
+ Reflect on what worked and keep building on it
179
+ </li>
180
+ </ul>
181
+ </Section>
182
+
183
+ <Section style={motivationBox}>
184
+ <Text style={motivationText}>
185
+ "Success is the sum of small efforts repeated day in and day out."
186
+ </Text>
187
+ <Text style={{ ...motivationText, marginTop: "8px", fontStyle: "normal" as const }}>
188
+ You proved it - congratulations! 💪
189
+ </Text>
190
+ </Section>
191
+
192
+ <div
193
+ style={{
194
+ textAlign: "left" as const,
195
+ margin: "24px 0",
196
+ }}
197
+ >
198
+ <a
199
+ href="https://www.discord.gg/trackedgg"
200
+ style={{
201
+ backgroundColor: "#5865F2",
202
+ borderRadius: "8px",
203
+ fontSize: "16px",
204
+ fontWeight: "bold",
205
+ textDecoration: "none",
206
+ padding: "12px 32px",
207
+ display: "inline-block",
208
+ }}
209
+ >
210
+ <span style={{ color: "#ffffff", textDecoration: "none" }}>
211
+ Join our Discord Community
212
+ </span>
213
+ </a>
214
+ </div>
215
+
216
+ <Hr style={hr} />
217
+ <Text style={footer}>
218
+ Copyright © Tracked Training Platform Inc. <br /> 9101 Horne
219
+ Street, Vancouver, BC
220
+ </Text>
221
+
222
+ <Container>
223
+ <Link
224
+ href={`${websiteUrl}/terms`}
225
+ style={{ ...footer, paddingRight: 10 }}
226
+ >
227
+ Terms
228
+ </Link>
229
+ <Link style={{ ...footer, paddingRight: 10 }}> | </Link>
230
+ <Link
231
+ href={`${websiteUrl}/privacy`}
232
+ style={{ ...footer, paddingRight: 10 }}
233
+ >
234
+ Privacy
235
+ </Link>
236
+ <Link style={{ ...footer, paddingRight: 10 }}> | </Link>
237
+ <Link
238
+ href={`${websiteUrl}/support`}
239
+ style={{ ...footer, paddingRight: 10 }}
240
+ >
241
+ Support
242
+ </Link>
243
+ </Container>
244
+
245
+ <Text style={footer}>
246
+ This is a service notification by the Tracked Training Platform.
247
+ </Text>
248
+ </Section>
249
+ </Container>
250
+ </Body>
251
+ </Html>
252
+ );
253
+ };
254
+
255
+ const main = {
256
+ backgroundColor: "#020617", // slate-950
257
+ fontFamily:
258
+ '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
259
+ };
260
+
261
+ const container = {
262
+ backgroundColor: "#020617", // slate-950
263
+ margin: "0 auto",
264
+ padding: "20px 0 48px",
265
+ marginBottom: "64px",
266
+ borderRadius: "8px",
267
+ };
268
+
269
+ const box = {
270
+ padding: "0 24px",
271
+ };
272
+
273
+ const hr = {
274
+ borderColor: "#4ade80", // green-400
275
+ margin: "24px 0",
276
+ borderWidth: "1px",
277
+ };
278
+
279
+ const paragraph = {
280
+ color: "#ffffff", // white
281
+ fontSize: "16px",
282
+ lineHeight: "24px",
283
+ textAlign: "left" as const,
284
+ };
285
+
286
+ const heading = {
287
+ color: "#ffffff", // white
288
+ fontSize: "32px",
289
+ lineHeight: "40px",
290
+ fontWeight: "bold",
291
+ marginBottom: "8px",
292
+ textAlign: "center" as const,
293
+ };
294
+
295
+ const celebrationText = {
296
+ color: "#4ade80",
297
+ fontSize: "18px",
298
+ lineHeight: "26px",
299
+ textAlign: "center" as const,
300
+ marginBottom: "24px",
301
+ };
302
+
303
+ const statsBox = {
304
+ backgroundColor: "#1e293b",
305
+ padding: "20px 24px",
306
+ borderRadius: "8px",
307
+ margin: "24px 0",
308
+ borderLeft: "4px solid #4ade80",
309
+ };
310
+
311
+ const statsHeading = {
312
+ color: "#4ade80",
313
+ fontSize: "16px",
314
+ fontWeight: "bold",
315
+ marginBottom: "16px",
316
+ textAlign: "center" as const,
317
+ };
318
+
319
+ const statsLabel = {
320
+ color: "#94a3b8",
321
+ fontSize: "12px",
322
+ textAlign: "center" as const,
323
+ marginBottom: "4px",
324
+ textTransform: "uppercase" as const,
325
+ };
326
+
327
+ const statsValue = {
328
+ color: "#ffffff",
329
+ fontSize: "20px",
330
+ fontWeight: "bold",
331
+ textAlign: "center" as const,
332
+ };
333
+
334
+ const changeBox = {
335
+ backgroundColor: "#0f172a",
336
+ padding: "12px 16px",
337
+ borderRadius: "6px",
338
+ marginTop: "16px",
339
+ };
340
+
341
+ const changeText = {
342
+ color: "#4ade80",
343
+ fontSize: "16px",
344
+ fontWeight: "bold",
345
+ textAlign: "center" as const,
346
+ margin: "0",
347
+ };
348
+
349
+ const nextStepsBox = {
350
+ backgroundColor: "#1e293b",
351
+ padding: "16px 24px",
352
+ borderRadius: "8px",
353
+ margin: "24px 0",
354
+ };
355
+
356
+ const nextStepsHeading = {
357
+ color: "#ffffff",
358
+ fontSize: "16px",
359
+ fontWeight: "bold",
360
+ marginBottom: "12px",
361
+ };
362
+
363
+ const nextStepsList = {
364
+ margin: "0",
365
+ paddingLeft: "20px",
366
+ };
367
+
368
+ const nextStepItem = {
369
+ color: "#e2e8f0",
370
+ fontSize: "14px",
371
+ lineHeight: "24px",
372
+ marginBottom: "8px",
373
+ };
374
+
375
+ const motivationBox = {
376
+ backgroundColor: "transparent",
377
+ padding: "16px 0",
378
+ margin: "24px 0",
379
+ borderLeft: "4px solid #4ade80",
380
+ paddingLeft: "20px",
381
+ };
382
+
383
+ const motivationText = {
384
+ color: "#94a3b8",
385
+ fontSize: "15px",
386
+ lineHeight: "22px",
387
+ fontStyle: "italic" as const,
388
+ };
389
+
390
+ const footer = {
391
+ color: "#94a3b8", // slate-400 for subtle footer text
392
+ fontSize: "12px",
393
+ lineHeight: "16px",
394
+ };
395
+
396
+ export default BodyweightGoalReachedEmail;