@tracked/emails 0.1.4 → 0.2.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 (53) hide show
  1. package/dist/emails/client-onboarded.d.ts +35 -0
  2. package/dist/emails/client-onboarded.d.ts.map +1 -0
  3. package/dist/emails/client-onboarded.js +152 -0
  4. package/dist/emails/client-onboarded.js.map +1 -0
  5. package/dist/emails/index.d.ts +1 -0
  6. package/dist/emails/index.d.ts.map +1 -1
  7. package/dist/emails/index.js +1 -0
  8. package/dist/emails/index.js.map +1 -1
  9. package/dist/emails/monthly-report.d.ts.map +1 -1
  10. package/dist/emails/monthly-report.js +31 -47
  11. package/dist/emails/monthly-report.js.map +1 -1
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +2 -2
  15. package/dist/index.js.map +1 -1
  16. package/package.json +20 -20
  17. package/src/components/content.tsx +351 -0
  18. package/src/components/index.ts +44 -0
  19. package/src/components/interactive.tsx +260 -0
  20. package/src/components/layout.tsx +217 -0
  21. package/src/components/tokens.ts +74 -0
  22. package/src/components/typography.tsx +148 -0
  23. package/src/emails/anniversary.tsx +133 -0
  24. package/src/emails/app-review-request.tsx +100 -0
  25. package/src/emails/bodyweight-goal-reached.tsx +202 -350
  26. package/src/emails/client-inactive-alert.tsx +130 -0
  27. package/src/emails/client-onboarded.tsx +272 -0
  28. package/src/emails/coach-invite.tsx +67 -250
  29. package/src/emails/coach-removed-client.tsx +36 -197
  30. package/src/emails/direct-message.tsx +69 -227
  31. package/src/emails/feature-discovery.tsx +82 -266
  32. package/src/emails/first-workout-assigned.tsx +52 -238
  33. package/src/emails/first-workout-completed.tsx +88 -294
  34. package/src/emails/inactive-reengagement.tsx +81 -0
  35. package/src/emails/index.tsx +1 -0
  36. package/src/emails/monthly-report.tsx +195 -525
  37. package/src/emails/new-follower.tsx +60 -238
  38. package/src/emails/nps-survey.tsx +149 -0
  39. package/src/emails/subscription-canceled.tsx +88 -294
  40. package/src/emails/support-email.tsx +33 -67
  41. package/src/emails/team-invite.tsx +47 -240
  42. package/src/emails/team-member-removed-email.tsx +23 -218
  43. package/src/emails/tracked-magic-link-activate.tsx +29 -237
  44. package/src/emails/tracked-magic-link.tsx +31 -251
  45. package/src/emails/week-one-checkin.tsx +108 -329
  46. package/src/emails/weekly-progress-digest.tsx +248 -0
  47. package/src/emails/welcome.tsx +58 -326
  48. package/src/index.ts +19 -2
  49. package/dist/emails/client-accepted-invitation.d.ts +0 -10
  50. package/dist/emails/client-accepted-invitation.d.ts.map +0 -1
  51. package/dist/emails/client-accepted-invitation.js +0 -99
  52. package/dist/emails/client-accepted-invitation.js.map +0 -1
  53. package/src/emails/client-accepted-invitation.tsx +0 -258
@@ -1,18 +1,18 @@
1
- import React from "react";
1
+ import * as React from "react";
2
+ import { Section, Text, Row, Column, Hr } from "@react-email/components";
2
3
  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";
4
+ EmailLayout,
5
+ EmailHeader,
6
+ EmailFooter,
7
+ MetricCard,
8
+ ChangeIndicator,
9
+ ListBox,
10
+ DataRow,
11
+ SectionHeading,
12
+ HighlightBanner,
13
+ SmallText,
14
+ colors,
15
+ } from "../components";
16
16
 
17
17
  interface MuscleGroup {
18
18
  muscleGroupId: string;
@@ -23,9 +23,7 @@ interface MuscleGroup {
23
23
  interface MonthlyMetrics {
24
24
  accountId: number;
25
25
  email: string;
26
- reportMonth: string; // YYYY-MM-01
27
-
28
- // Metrics
26
+ reportMonth: string;
29
27
  avgStepCount: number | null;
30
28
  avgBodyweight: number | null;
31
29
  bodyweightChange: number | null;
@@ -33,8 +31,6 @@ interface MonthlyMetrics {
33
31
  totalSetsTracked: number | null;
34
32
  totalSessionsTracked: number | null;
35
33
  topMuscleGroups: Array<MuscleGroup> | null;
36
-
37
- // Survey metrics (optional)
38
34
  avgReadinessEnergy: number | null;
39
35
  avgReadinessMood: number | null;
40
36
  avgReadinessStress: number | null;
@@ -59,10 +55,9 @@ interface MonthlyReportEmailProps {
59
55
  metrics?: MonthlyMetrics;
60
56
  previousMetrics?: PreviousMetrics | null;
61
57
  websiteUrl?: string;
58
+ unsubscribeUrl?: string;
62
59
  }
63
60
 
64
- const baseUrl = "https://tracked.gg/android-chrome-192x192.png";
65
-
66
61
  function formatNumber(num: number | null): string {
67
62
  if (num === null) return "N/A";
68
63
  return num.toLocaleString();
@@ -119,32 +114,10 @@ export const MonthlyReportEmail = ({
119
114
  metrics = defaultMetrics,
120
115
  previousMetrics = defaultPreviousMetrics,
121
116
  websiteUrl = "https://tracked.gg",
117
+ unsubscribeUrl,
122
118
  }: MonthlyReportEmailProps) => {
123
119
  const monthName = getMonthName(metrics.reportMonth);
124
120
 
125
- const getChangeIndicator = (
126
- current: number | null,
127
- previous: number | null,
128
- ): React.ReactNode => {
129
- if (current === null || previous === null || !previousMetrics) return null;
130
- const diff = current - previous;
131
- if (diff > 0) {
132
- return (
133
- <Text style={changeIndicatorPositive}>
134
- ▲ {formatDecimal(Math.abs(diff))}
135
- </Text>
136
- );
137
- }
138
- if (diff < 0) {
139
- return (
140
- <Text style={changeIndicatorNegative}>
141
- ▼ {formatDecimal(Math.abs(diff))}
142
- </Text>
143
- );
144
- }
145
- return <Text style={changeIndicatorNeutral}>—</Text>;
146
- };
147
-
148
121
  const hasBodyweightData = metrics.avgBodyweight || metrics.bodyweightChange;
149
122
  const hasSessionInsights =
150
123
  metrics.avgReadinessEnergy ||
@@ -153,493 +126,190 @@ export const MonthlyReportEmail = ({
153
126
  metrics.avgSessionProgress;
154
127
 
155
128
  return (
156
- <Html>
157
- <Head>
158
- <meta name="color-scheme" content="light only" />
159
- <meta name="supported-color-schemes" content="light only" />
160
- </Head>
161
- <Preview>Your {monthName} Training Report - Your monthly fitness journey at a glance</Preview>
162
- <Body style={main}>
163
- <Container style={container}>
164
- <Section style={box}>
165
- {/* Header */}
166
- <Row style={{ marginBottom: "8px" }}>
167
- <Column style={{ width: "auto", verticalAlign: "middle" }}>
168
- <Img src={`${baseUrl}`} width="28" height="28" alt="Tracked" />
129
+ <EmailLayout
130
+ preview={`Your ${monthName} Training Report - Your monthly fitness journey at a glance`}
131
+ >
132
+ <EmailHeader />
133
+
134
+ <HighlightBanner>
135
+ <Text
136
+ style={{
137
+ color: "#ffffff",
138
+ margin: "0",
139
+ fontSize: "20px",
140
+ fontWeight: "700" as const,
141
+ }}
142
+ >
143
+ Your {monthName} Training Report
144
+ </Text>
145
+ </HighlightBanner>
146
+
147
+ {/* Activity Overview */}
148
+ <Section style={{ marginBottom: "24px" }}>
149
+ <SectionHeading>Activity Overview</SectionHeading>
150
+ <MetricCard
151
+ label="Average Daily Steps"
152
+ value={formatNumber(metrics.avgStepCount)}
153
+ change={
154
+ <ChangeIndicator
155
+ value={metrics.avgStepCount}
156
+ previousValue={previousMetrics?.avgStepCount || null}
157
+ />
158
+ }
159
+ />
160
+ </Section>
161
+
162
+ {/* Bodyweight Progress */}
163
+ {hasBodyweightData && (
164
+ <>
165
+ <Hr style={{ borderColor: colors.border, margin: "24px 0" }} />
166
+ <Section style={{ marginBottom: "24px" }}>
167
+ <SectionHeading>Bodyweight Trends</SectionHeading>
168
+ <Row>
169
+ <Column style={{ width: "48%" }}>
170
+ <MetricCard
171
+ label="Average Bodyweight"
172
+ value={`${formatDecimal(metrics.avgBodyweight, 1)} lbs`}
173
+ size="small"
174
+ change={
175
+ <ChangeIndicator
176
+ value={metrics.avgBodyweight}
177
+ previousValue={previousMetrics?.avgBodyweight || null}
178
+ />
179
+ }
180
+ />
169
181
  </Column>
170
- <Column
171
- style={{
172
- width: "auto",
173
- verticalAlign: "middle",
174
- paddingLeft: "4px",
175
- }}
176
- >
177
- <Text style={logo}>TRACKED</Text>
182
+ <Column style={{ width: "4%" }} />
183
+ <Column style={{ width: "48%" }}>
184
+ <MetricCard
185
+ label="Monthly Change"
186
+ value={
187
+ metrics.bodyweightChange !== null
188
+ ? (metrics.bodyweightChange >= 0 ? "+" : "") +
189
+ formatDecimal(metrics.bodyweightChange, 1) +
190
+ " lbs"
191
+ : "N/A"
192
+ }
193
+ size="small"
194
+ />
178
195
  </Column>
179
196
  </Row>
180
-
181
- {/* Purple Gradient Section */}
182
- <Section style={headerSection}>
183
- <Text style={headerTitle}>Your {monthName} Training Report</Text>
184
- <Text style={headerSubtitle}>
185
- Your monthly fitness journey at a glance
186
- </Text>
187
- </Section>
188
-
189
- <Hr style={hr} />
190
-
191
- {/* Activity Overview */}
192
- <Section style={sectionContainer}>
193
- <Text style={sectionHeading}>📊 Activity Overview</Text>
194
- <Section style={metricBox}>
195
- <Text style={metricLabel}>Average Daily Steps</Text>
196
- <Row>
197
- <Column>
198
- <Text style={metricValue}>
199
- {formatNumber(metrics.avgStepCount)}
200
- </Text>
201
- </Column>
202
- {getChangeIndicator(
203
- metrics.avgStepCount,
204
- previousMetrics?.avgStepCount || null,
205
- )}
206
- </Row>
207
- </Section>
208
- </Section>
209
-
210
- {/* Bodyweight Progress */}
211
- {hasBodyweightData && (
212
- <>
213
- <Hr style={hr} />
214
- <Section style={sectionContainer}>
215
- <Text style={sectionHeading}>⚖️ Bodyweight Trends</Text>
216
- <Row>
217
- <Column style={{ width: "48%" }}>
218
- <Section style={metricBox}>
219
- <Text style={metricLabel}>Average Bodyweight</Text>
220
- <Row>
221
- <Column>
222
- <Text style={metricValueSmall}>
223
- {formatDecimal(metrics.avgBodyweight, 1)} lbs
224
- </Text>
225
- </Column>
226
- {getChangeIndicator(
227
- metrics.avgBodyweight,
228
- previousMetrics?.avgBodyweight || null,
229
- )}
230
- </Row>
231
- </Section>
232
- </Column>
233
- <Column style={{ width: "4%" }} />
234
- <Column style={{ width: "48%" }}>
235
- <Section style={metricBox}>
236
- <Text style={metricLabel}>Monthly Change</Text>
237
- <Text style={metricValueSmall}>
238
- {metrics.bodyweightChange !== null
239
- ? (metrics.bodyweightChange >= 0 ? "+" : "") +
240
- formatDecimal(metrics.bodyweightChange, 1) +
241
- " lbs"
242
- : "N/A"}
243
- </Text>
244
- </Section>
245
- </Column>
246
- </Row>
247
- </Section>
248
- </>
249
- )}
250
-
251
- {/* Training Summary */}
252
- <Hr style={hr} />
253
- <Section style={sectionContainer}>
254
- <Text style={sectionHeading}>💪 Training Summary</Text>
255
- <Row>
256
- <Column style={{ width: "48%" }}>
257
- <Section style={metricBox}>
258
- <Text style={metricLabel}>Sessions Per Week</Text>
259
- <Row>
260
- <Column>
261
- <Text style={metricValueSmall}>
262
- {formatDecimal(metrics.avgSessionsPerWeek, 1)}
263
- </Text>
264
- </Column>
265
- {getChangeIndicator(
266
- metrics.avgSessionsPerWeek,
267
- previousMetrics?.avgSessionsPerWeek || null,
268
- )}
269
- </Row>
270
- </Section>
271
- </Column>
272
- <Column style={{ width: "4%" }} />
273
- <Column style={{ width: "48%" }}>
274
- <Section style={metricBox}>
275
- <Text style={metricLabel}>Total Sessions</Text>
276
- <Row>
277
- <Column>
278
- <Text style={metricValueSmall}>
279
- {formatNumber(metrics.totalSessionsTracked)}
280
- </Text>
281
- </Column>
282
- {getChangeIndicator(
283
- metrics.totalSessionsTracked,
284
- previousMetrics?.totalSessionsTracked || null,
285
- )}
286
- </Row>
287
- </Section>
288
- </Column>
289
- </Row>
290
- <Section style={{ ...metricBox, marginTop: "10px" }}>
291
- <Text style={metricLabel}>Total Sets Tracked</Text>
292
- <Row>
293
- <Column>
294
- <Text style={metricValue}>
295
- {formatNumber(metrics.totalSetsTracked)}
296
- </Text>
297
- </Column>
298
- {getChangeIndicator(
299
- metrics.totalSetsTracked,
300
- previousMetrics?.totalSetsTracked || null,
301
- )}
302
- </Row>
303
- </Section>
304
- </Section>
305
-
306
- {/* Top Muscle Groups */}
307
- {metrics.topMuscleGroups && metrics.topMuscleGroups.length > 0 && (
308
- <>
309
- <Hr style={hr} />
310
- <Section style={sectionContainer}>
311
- <Text style={sectionHeading}>
312
- 🎯 Top 5 Prioritized Muscle Groups
313
- </Text>
314
- <Section style={listBox}>
315
- {metrics.topMuscleGroups.map((mg, idx) => (
316
- <Row
317
- key={mg.muscleGroupId}
318
- style={{
319
- ...listItem,
320
- borderBottom:
321
- idx < metrics.topMuscleGroups!.length - 1
322
- ? "1px solid #334155"
323
- : "none",
324
- }}
325
- >
326
- <Column>
327
- <Text style={listItemName}>
328
- {idx + 1}. {mg.name}
329
- </Text>
330
- </Column>
331
- <Column style={{ textAlign: "right" }}>
332
- <Text style={listItemValue}>
333
- {formatNumber(mg.sets)} sets
334
- </Text>
335
- </Column>
336
- </Row>
337
- ))}
338
- </Section>
339
- </Section>
340
- </>
341
- )}
342
-
343
- {/* Session Insights */}
344
- {hasSessionInsights && (
345
- <>
346
- <Hr style={hr} />
347
- <Section style={sectionContainer}>
348
- <Text style={sectionHeading}>📈 Session Insights</Text>
349
- <Section style={listBox}>
350
- {metrics.avgReadinessEnergy && (
351
- <Row style={insightRow}>
352
- <Column>
353
- <Text style={insightLabel}>
354
- Avg Readiness Energy
355
- </Text>
356
- </Column>
357
- <Column style={{ textAlign: "right" }}>
358
- <Text style={insightValue}>
359
- {formatDecimal(metrics.avgReadinessEnergy)}/10
360
- </Text>
361
- </Column>
362
- </Row>
363
- )}
364
- {metrics.avgReadinessMood && (
365
- <Row style={insightRow}>
366
- <Column>
367
- <Text style={insightLabel}>Avg Readiness Mood</Text>
368
- </Column>
369
- <Column style={{ textAlign: "right" }}>
370
- <Text style={insightValue}>
371
- {formatDecimal(metrics.avgReadinessMood)}/10
372
- </Text>
373
- </Column>
374
- </Row>
375
- )}
376
- {metrics.avgSessionSatisfaction && (
377
- <Row style={insightRow}>
378
- <Column>
379
- <Text style={insightLabel}>
380
- Avg Session Satisfaction
381
- </Text>
382
- </Column>
383
- <Column style={{ textAlign: "right" }}>
384
- <Text style={insightValue}>
385
- {formatDecimal(metrics.avgSessionSatisfaction)}/10
386
- </Text>
387
- </Column>
388
- </Row>
389
- )}
390
- {metrics.avgSessionProgress && (
391
- <Row style={insightRow}>
392
- <Column>
393
- <Text style={insightLabel}>
394
- Avg Session Progress
395
- </Text>
396
- </Column>
397
- <Column style={{ textAlign: "right" }}>
398
- <Text style={insightValue}>
399
- {formatDecimal(metrics.avgSessionProgress)}/10
400
- </Text>
401
- </Column>
402
- </Row>
403
- )}
404
- </Section>
405
- </Section>
406
- </>
407
- )}
408
-
409
- {/* Footer Message */}
410
- <Hr style={hr} />
411
- <Section style={footerMessageBox}>
412
- <Text style={footerMessage}>
413
- Keep up the great work! Every rep, every session, every step
414
- counts towards your goals.
415
- </Text>
416
- <Text style={footerMessageSmall}>
417
- You can manage your email preferences in the Tracked app
418
- settings.
419
- </Text>
420
- </Section>
421
-
422
- <Hr style={hr} />
423
-
424
- {/* Footer Links */}
425
- <Text style={footer}>
426
- Copyright © {new Date().getFullYear()} Tracked Training Platform Inc. <br /> 9101 Horne
427
- Street, Vancouver, BC
428
- </Text>
429
-
430
- <Container>
431
- <Link
432
- href={`${websiteUrl}/terms`}
433
- style={{ ...footer, paddingRight: 10 }}
434
- >
435
- Terms
436
- </Link>
437
- <Link style={{ ...footer, paddingRight: 10 }}> | </Link>
438
- <Link
439
- href={`${websiteUrl}/privacy`}
440
- style={{ ...footer, paddingRight: 10 }}
441
- >
442
- Privacy
443
- </Link>
444
- <Link style={{ ...footer, paddingRight: 10 }}> | </Link>
445
- <Link
446
- href={`${websiteUrl}/support`}
447
- style={{ ...footer, paddingRight: 10 }}
448
- >
449
- Support
450
- </Link>
451
- </Container>
452
-
453
- <Text style={footer}>
454
- This is a service notification by the Tracked Training Platform.
455
- </Text>
456
197
  </Section>
457
- </Container>
458
- </Body>
459
- </Html>
460
- );
461
- };
462
-
463
- // Styles
464
- const main = {
465
- backgroundColor: "#020617", // slate-950
466
- fontFamily:
467
- "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
468
- };
469
-
470
- const container = {
471
- backgroundColor: "#020617", // slate-950
472
- margin: "0 auto",
473
- padding: "20px 0 48px",
474
- marginBottom: "64px",
475
- borderRadius: "8px",
476
- };
477
-
478
- const box = {
479
- padding: "0 24px 24px 24px",
480
- };
481
-
482
- const logo = {
483
- fontSize: "28px",
484
- fontWeight: "900",
485
- fontFamily:
486
- "Raleway, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
487
- color: "#ffffff",
488
- margin: "0",
489
- lineHeight: "32px",
490
- letterSpacing: "0.5px",
491
- };
492
-
493
- const headerSection = {
494
- background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
495
- padding: "40px 20px",
496
- textAlign: "center" as const,
497
- borderRadius: "8px",
498
- margin: "20px 0",
499
- };
500
-
501
- const headerTitle = {
502
- color: "#ffffff",
503
- margin: "0",
504
- fontSize: "28px",
505
- fontWeight: "700",
506
- };
507
-
508
- const headerSubtitle = {
509
- color: "#e9d5ff",
510
- margin: "10px 0 0 0",
511
- fontSize: "16px",
512
- };
513
-
514
- const hr = {
515
- borderColor: "#4ade80", // green-400
516
- margin: "24px 0",
517
- borderWidth: "1px",
518
- };
519
-
520
- const sectionContainer = {
521
- marginBottom: "20px",
522
- };
523
-
524
- const sectionHeading = {
525
- color: "#ffffff",
526
- fontSize: "20px",
527
- margin: "0 0 20px 0",
528
- fontWeight: "600",
529
- };
530
-
531
- const metricBox = {
532
- backgroundColor: "#1e293b", // slate-800
533
- padding: "15px",
534
- borderRadius: "8px",
535
- };
536
-
537
- const metricLabel = {
538
- color: "#94a3b8", // slate-400
539
- fontSize: "14px",
540
- marginBottom: "5px",
541
- };
542
-
543
- const metricValue = {
544
- color: "#ffffff",
545
- fontSize: "24px",
546
- fontWeight: "700",
547
- margin: "0",
548
- };
549
-
550
- const metricValueSmall = {
551
- color: "#ffffff",
552
- fontSize: "20px",
553
- fontWeight: "700",
554
- margin: "0",
555
- };
556
-
557
- const changeIndicatorPositive = {
558
- color: "#10b981",
559
- fontSize: "14px",
560
- marginLeft: "8px",
561
- display: "inline",
562
- };
563
-
564
- const changeIndicatorNegative = {
565
- color: "#ef4444",
566
- fontSize: "14px",
567
- marginLeft: "8px",
568
- display: "inline",
569
- };
570
-
571
- const changeIndicatorNeutral = {
572
- color: "#6b7280",
573
- fontSize: "14px",
574
- marginLeft: "8px",
575
- display: "inline",
576
- };
577
-
578
- const listBox = {
579
- backgroundColor: "#1e293b", // slate-800
580
- borderRadius: "8px",
581
- overflow: "hidden",
582
- };
583
-
584
- const listItem = {
585
- padding: "12px",
586
- };
587
-
588
- const listItemName = {
589
- color: "#ffffff",
590
- fontSize: "16px",
591
- fontWeight: "600",
592
- margin: "0",
593
- };
594
-
595
- const listItemValue = {
596
- color: "#e2e8f0", // slate-200
597
- fontSize: "16px",
598
- margin: "0",
599
- };
600
-
601
- const insightRow = {
602
- padding: "10px",
603
- };
604
-
605
- const insightLabel = {
606
- color: "#94a3b8", // slate-400
607
- fontSize: "14px",
608
- margin: "0",
609
- };
610
-
611
- const insightValue = {
612
- color: "#ffffff",
613
- fontSize: "16px",
614
- fontWeight: "600",
615
- margin: "0",
616
- };
617
-
618
- const footerMessageBox = {
619
- marginTop: "20px",
620
- padding: "20px",
621
- backgroundColor: "#1e293b", // slate-800
622
- borderRadius: "8px",
623
- textAlign: "center" as const,
624
- };
625
-
626
- const footerMessage = {
627
- color: "#e2e8f0", // slate-200
628
- fontSize: "14px",
629
- margin: "0",
630
- };
198
+ </>
199
+ )}
200
+
201
+ {/* Training Summary */}
202
+ <Hr style={{ borderColor: colors.border, margin: "24px 0" }} />
203
+ <Section style={{ marginBottom: "24px" }}>
204
+ <SectionHeading>Training Summary</SectionHeading>
205
+ <Row>
206
+ <Column style={{ width: "48%" }}>
207
+ <MetricCard
208
+ label="Sessions Per Week"
209
+ value={formatDecimal(metrics.avgSessionsPerWeek, 1)}
210
+ size="small"
211
+ change={
212
+ <ChangeIndicator
213
+ value={metrics.avgSessionsPerWeek}
214
+ previousValue={previousMetrics?.avgSessionsPerWeek || null}
215
+ />
216
+ }
217
+ />
218
+ </Column>
219
+ <Column style={{ width: "4%" }} />
220
+ <Column style={{ width: "48%" }}>
221
+ <MetricCard
222
+ label="Total Sessions"
223
+ value={formatNumber(metrics.totalSessionsTracked)}
224
+ size="small"
225
+ change={
226
+ <ChangeIndicator
227
+ value={metrics.totalSessionsTracked}
228
+ previousValue={previousMetrics?.totalSessionsTracked || null}
229
+ />
230
+ }
231
+ />
232
+ </Column>
233
+ </Row>
234
+ <Section style={{ marginTop: "10px" }}>
235
+ <MetricCard
236
+ label="Total Sets Tracked"
237
+ value={formatNumber(metrics.totalSetsTracked)}
238
+ change={
239
+ <ChangeIndicator
240
+ value={metrics.totalSetsTracked}
241
+ previousValue={previousMetrics?.totalSetsTracked || null}
242
+ />
243
+ }
244
+ />
245
+ </Section>
246
+ </Section>
247
+
248
+ {/* Top Muscle Groups */}
249
+ {metrics.topMuscleGroups && metrics.topMuscleGroups.length > 0 && (
250
+ <>
251
+ <Hr style={{ borderColor: colors.border, margin: "24px 0" }} />
252
+ <Section style={{ marginBottom: "24px" }}>
253
+ <SectionHeading>Top 5 Prioritized Muscle Groups</SectionHeading>
254
+ <ListBox>
255
+ {metrics.topMuscleGroups.map((mg, idx) => (
256
+ <DataRow
257
+ key={mg.muscleGroupId}
258
+ label={`${idx + 1}. ${mg.name}`}
259
+ value={`${formatNumber(mg.sets)} sets`}
260
+ isLast={idx === metrics.topMuscleGroups!.length - 1}
261
+ />
262
+ ))}
263
+ </ListBox>
264
+ </Section>
265
+ </>
266
+ )}
267
+
268
+ {/* Session Insights */}
269
+ {hasSessionInsights && (
270
+ <>
271
+ <Hr style={{ borderColor: colors.border, margin: "24px 0" }} />
272
+ <Section style={{ marginBottom: "24px" }}>
273
+ <SectionHeading>Session Insights</SectionHeading>
274
+ <ListBox>
275
+ {metrics.avgReadinessEnergy && (
276
+ <DataRow
277
+ label="Avg Readiness Energy"
278
+ value={`${formatDecimal(metrics.avgReadinessEnergy)}/10`}
279
+ />
280
+ )}
281
+ {metrics.avgReadinessMood && (
282
+ <DataRow
283
+ label="Avg Readiness Mood"
284
+ value={`${formatDecimal(metrics.avgReadinessMood)}/10`}
285
+ />
286
+ )}
287
+ {metrics.avgSessionSatisfaction && (
288
+ <DataRow
289
+ label="Avg Session Satisfaction"
290
+ value={`${formatDecimal(metrics.avgSessionSatisfaction)}/10`}
291
+ />
292
+ )}
293
+ {metrics.avgSessionProgress && (
294
+ <DataRow
295
+ label="Avg Session Progress"
296
+ value={`${formatDecimal(metrics.avgSessionProgress)}/10`}
297
+ isLast
298
+ />
299
+ )}
300
+ </ListBox>
301
+ </Section>
302
+ </>
303
+ )}
631
304
 
632
- const footerMessageSmall = {
633
- color: "#94a3b8", // slate-400
634
- fontSize: "12px",
635
- margin: "15px 0 0 0",
636
- };
305
+ <Hr style={{ borderColor: colors.border, margin: "24px 0" }} />
306
+ <SmallText muted>
307
+ You can manage your email preferences in the Tracked app settings.
308
+ </SmallText>
637
309
 
638
- const footer = {
639
- color: "#94a3b8", // slate-400
640
- fontSize: "12px",
641
- lineHeight: "16px",
642
- textAlign: "center" as const,
310
+ <EmailFooter websiteUrl={websiteUrl} marketing unsubscribeUrl={unsubscribeUrl} />
311
+ </EmailLayout>
312
+ );
643
313
  };
644
314
 
645
315
  export default MonthlyReportEmail;