@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.
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/dist/emails/bodyweight-goal-reached.d.ts +14 -0
- package/dist/emails/bodyweight-goal-reached.d.ts.map +1 -0
- package/dist/emails/bodyweight-goal-reached.js +177 -0
- package/dist/emails/bodyweight-goal-reached.js.map +1 -0
- package/dist/emails/client-accepted-invitation.d.ts +10 -0
- package/dist/emails/client-accepted-invitation.d.ts.map +1 -0
- package/dist/emails/client-accepted-invitation.js +99 -0
- package/dist/emails/client-accepted-invitation.js.map +1 -0
- package/dist/emails/coach-invite.d.ts +10 -0
- package/dist/emails/coach-invite.d.ts.map +1 -0
- package/dist/emails/coach-invite.js +126 -0
- package/dist/emails/coach-invite.js.map +1 -0
- package/dist/emails/coach-removed-client.d.ts +8 -0
- package/dist/emails/coach-removed-client.d.ts.map +1 -0
- package/dist/emails/coach-removed-client.js +80 -0
- package/dist/emails/coach-removed-client.js.map +1 -0
- package/dist/emails/direct-message.d.ts +11 -0
- package/dist/emails/direct-message.d.ts.map +1 -0
- package/dist/emails/direct-message.js +103 -0
- package/dist/emails/direct-message.js.map +1 -0
- package/dist/emails/feature-discovery.d.ts +11 -0
- package/dist/emails/feature-discovery.d.ts.map +1 -0
- package/dist/emails/feature-discovery.js +121 -0
- package/dist/emails/feature-discovery.js.map +1 -0
- package/dist/emails/first-workout-assigned.d.ts +10 -0
- package/dist/emails/first-workout-assigned.d.ts.map +1 -0
- package/dist/emails/first-workout-assigned.js +98 -0
- package/dist/emails/first-workout-assigned.js.map +1 -0
- package/dist/emails/first-workout-completed.d.ts +11 -0
- package/dist/emails/first-workout-completed.d.ts.map +1 -0
- package/dist/emails/first-workout-completed.js +129 -0
- package/dist/emails/first-workout-completed.js.map +1 -0
- package/dist/emails/index.d.ts +7 -0
- package/dist/emails/index.d.ts.map +1 -0
- package/dist/emails/index.js +7 -0
- package/dist/emails/index.js.map +1 -0
- package/dist/emails/new-follower.d.ts +11 -0
- package/dist/emails/new-follower.d.ts.map +1 -0
- package/dist/emails/new-follower.js +98 -0
- package/dist/emails/new-follower.js.map +1 -0
- package/dist/emails/subscription-canceled.d.ts +10 -0
- package/dist/emails/subscription-canceled.d.ts.map +1 -0
- package/dist/emails/subscription-canceled.js +131 -0
- package/dist/emails/subscription-canceled.js.map +1 -0
- package/dist/emails/support-email.d.ts +8 -0
- package/dist/emails/support-email.d.ts.map +1 -0
- package/dist/emails/support-email.js +40 -0
- package/dist/emails/support-email.js.map +1 -0
- package/dist/emails/team-invite.d.ts +11 -0
- package/dist/emails/team-invite.d.ts.map +1 -0
- package/dist/emails/team-invite.js +100 -0
- package/dist/emails/team-invite.js.map +1 -0
- package/dist/emails/team-member-removed-email.d.ts +8 -0
- package/dist/emails/team-member-removed-email.d.ts.map +1 -0
- package/dist/emails/team-member-removed-email.js +97 -0
- package/dist/emails/team-member-removed-email.js.map +1 -0
- package/dist/emails/tracked-magic-link-activate.d.ts +7 -0
- package/dist/emails/tracked-magic-link-activate.d.ts.map +1 -0
- package/dist/emails/tracked-magic-link-activate.js +93 -0
- package/dist/emails/tracked-magic-link-activate.js.map +1 -0
- package/dist/emails/tracked-magic-link.d.ts +7 -0
- package/dist/emails/tracked-magic-link.d.ts.map +1 -0
- package/dist/emails/tracked-magic-link.js +101 -0
- package/dist/emails/tracked-magic-link.js.map +1 -0
- package/dist/emails/week-one-checkin.d.ts +10 -0
- package/dist/emails/week-one-checkin.d.ts.map +1 -0
- package/dist/emails/week-one-checkin.js +141 -0
- package/dist/emails/week-one-checkin.js.map +1 -0
- package/dist/emails/welcome.d.ts +8 -0
- package/dist/emails/welcome.d.ts.map +1 -0
- package/dist/emails/welcome.js +146 -0
- package/dist/emails/welcome.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/email-validation.d.ts +48 -0
- package/dist/utils/email-validation.d.ts.map +1 -0
- package/dist/utils/email-validation.js +72 -0
- package/dist/utils/email-validation.js.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/username-validation.d.ts +54 -0
- package/dist/utils/username-validation.d.ts.map +1 -0
- package/dist/utils/username-validation.js +76 -0
- package/dist/utils/username-validation.js.map +1 -0
- package/package.json +78 -0
- package/src/emails/bodyweight-goal-reached.tsx +396 -0
- package/src/emails/client-accepted-invitation.tsx +258 -0
- package/src/emails/coach-invite.tsx +270 -0
- package/src/emails/coach-removed-client.tsx +212 -0
- package/src/emails/direct-message.tsx +249 -0
- package/src/emails/feature-discovery.tsx +289 -0
- package/src/emails/first-workout-assigned.tsx +255 -0
- package/src/emails/first-workout-completed.tsx +312 -0
- package/src/emails/index.tsx +6 -0
- package/src/emails/new-follower.tsx +260 -0
- package/src/emails/subscription-canceled.tsx +311 -0
- package/src/emails/support-email.tsx +80 -0
- package/src/emails/team-invite.tsx +262 -0
- package/src/emails/team-member-removed-email.tsx +240 -0
- package/src/emails/tracked-magic-link-activate.tsx +252 -0
- package/src/emails/tracked-magic-link.tsx +264 -0
- package/src/emails/week-one-checkin.tsx +353 -0
- package/src/emails/welcome.tsx +341 -0
- package/src/index.ts +57 -0
- package/src/utils/email-validation.test.ts +78 -0
- package/src/utils/email-validation.ts +80 -0
- package/src/utils/index.ts +13 -0
- package/src/utils/username-validation.test.ts +118 -0
- package/src/utils/username-validation.ts +89 -0
- 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;
|