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