@burdenoff/microfe-mybodyshield 2026.827.1 → 2026.830.1

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 (90) hide show
  1. package/dist/mybodyshield/MyBodyShieldRoot.d.ts.map +1 -1
  2. package/dist/mybodyshield/MyBodyShieldRoot.js +54 -56
  3. package/dist/mybodyshield/MyBodyShieldRoot.js.map +1 -1
  4. package/dist/mybodyshield/MyBodyShieldRoutes.d.ts.map +1 -1
  5. package/dist/mybodyshield/MyBodyShieldRoutes.js +121 -62
  6. package/dist/mybodyshield/MyBodyShieldRoutes.js.map +1 -1
  7. package/dist/mybodyshield/components/EvidenceUpload.d.ts.map +1 -1
  8. package/dist/mybodyshield/components/EvidenceUpload.js +46 -45
  9. package/dist/mybodyshield/components/EvidenceUpload.js.map +1 -1
  10. package/dist/mybodyshield/components/FileLink.d.ts.map +1 -1
  11. package/dist/mybodyshield/components/FileLink.js +27 -18
  12. package/dist/mybodyshield/components/FileLink.js.map +1 -1
  13. package/dist/mybodyshield/components/MybDiscussion.d.ts +1 -1
  14. package/dist/mybodyshield/components/MybDiscussion.d.ts.map +1 -1
  15. package/dist/mybodyshield/components/MybDiscussion.js +59 -58
  16. package/dist/mybodyshield/components/MybDiscussion.js.map +1 -1
  17. package/dist/mybodyshield/components/MybTags.d.ts +1 -1
  18. package/dist/mybodyshield/components/MybTags.d.ts.map +1 -1
  19. package/dist/mybodyshield/components/MybTags.js +83 -74
  20. package/dist/mybodyshield/components/MybTags.js.map +1 -1
  21. package/dist/mybodyshield/components/PageDisabled.d.ts.map +1 -1
  22. package/dist/mybodyshield/components/PageDisabled.js +16 -11
  23. package/dist/mybodyshield/components/PageDisabled.js.map +1 -1
  24. package/dist/mybodyshield/components/Pagination.d.ts.map +1 -1
  25. package/dist/mybodyshield/components/Pagination.js +36 -27
  26. package/dist/mybodyshield/components/Pagination.js.map +1 -1
  27. package/dist/mybodyshield/components/StateViews.d.ts.map +1 -1
  28. package/dist/mybodyshield/components/StateViews.js +42 -34
  29. package/dist/mybodyshield/components/StateViews.js.map +1 -1
  30. package/dist/mybodyshield/components/UserSearchSelect.d.ts.map +1 -1
  31. package/dist/mybodyshield/components/UserSearchSelect.js +66 -68
  32. package/dist/mybodyshield/components/UserSearchSelect.js.map +1 -1
  33. package/dist/mybodyshield/hooks/index.d.ts +1 -0
  34. package/dist/mybodyshield/hooks/index.d.ts.map +1 -1
  35. package/dist/mybodyshield/hooks/index.js +1 -0
  36. package/dist/mybodyshield/hooks/useTr.d.ts +5 -0
  37. package/dist/mybodyshield/hooks/useTr.d.ts.map +1 -0
  38. package/dist/mybodyshield/hooks/useTr.js +12 -0
  39. package/dist/mybodyshield/hooks/useTr.js.map +1 -0
  40. package/dist/mybodyshield/pages/DashboardPage.d.ts.map +1 -1
  41. package/dist/mybodyshield/pages/DashboardPage.js +157 -136
  42. package/dist/mybodyshield/pages/DashboardPage.js.map +1 -1
  43. package/dist/mybodyshield/pages/SettingsPage.d.ts.map +1 -1
  44. package/dist/mybodyshield/pages/SettingsPage.js +50 -59
  45. package/dist/mybodyshield/pages/SettingsPage.js.map +1 -1
  46. package/dist/mybodyshield/pages/_shared/visuals.d.ts +2 -1
  47. package/dist/mybodyshield/pages/_shared/visuals.d.ts.map +1 -1
  48. package/dist/mybodyshield/pages/_shared/visuals.js +91 -81
  49. package/dist/mybodyshield/pages/_shared/visuals.js.map +1 -1
  50. package/dist/mybodyshield/pages/ai/AiControlsPage.d.ts.map +1 -1
  51. package/dist/mybodyshield/pages/ai/AiControlsPage.js +255 -266
  52. package/dist/mybodyshield/pages/ai/AiControlsPage.js.map +1 -1
  53. package/dist/mybodyshield/pages/coaching/CoachingPage.d.ts.map +1 -1
  54. package/dist/mybodyshield/pages/coaching/CoachingPage.js +321 -301
  55. package/dist/mybodyshield/pages/coaching/CoachingPage.js.map +1 -1
  56. package/dist/mybodyshield/pages/goals/GoalsPage.d.ts.map +1 -1
  57. package/dist/mybodyshield/pages/goals/GoalsPage.js +247 -251
  58. package/dist/mybodyshield/pages/goals/GoalsPage.js.map +1 -1
  59. package/dist/mybodyshield/pages/insights/InsightsCharts.d.ts.map +1 -1
  60. package/dist/mybodyshield/pages/insights/InsightsCharts.js +216 -213
  61. package/dist/mybodyshield/pages/insights/InsightsCharts.js.map +1 -1
  62. package/dist/mybodyshield/pages/insights/InsightsPage.d.ts.map +1 -1
  63. package/dist/mybodyshield/pages/insights/InsightsPage.js +290 -284
  64. package/dist/mybodyshield/pages/insights/InsightsPage.js.map +1 -1
  65. package/dist/mybodyshield/pages/nutrition/NutritionPage.d.ts.map +1 -1
  66. package/dist/mybodyshield/pages/nutrition/NutritionPage.js +575 -529
  67. package/dist/mybodyshield/pages/nutrition/NutritionPage.js.map +1 -1
  68. package/dist/mybodyshield/pages/programs/ProgramDetailPage.d.ts.map +1 -1
  69. package/dist/mybodyshield/pages/programs/ProgramDetailPage.js +333 -275
  70. package/dist/mybodyshield/pages/programs/ProgramDetailPage.js.map +1 -1
  71. package/dist/mybodyshield/pages/programs/ProgramsPage.d.ts.map +1 -1
  72. package/dist/mybodyshield/pages/programs/ProgramsPage.js +258 -253
  73. package/dist/mybodyshield/pages/programs/ProgramsPage.js.map +1 -1
  74. package/dist/mybodyshield/pages/progress/ProgressPage.d.ts.map +1 -1
  75. package/dist/mybodyshield/pages/progress/ProgressPage.js +271 -254
  76. package/dist/mybodyshield/pages/progress/ProgressPage.js.map +1 -1
  77. package/dist/mybodyshield/pages/recovery/RecoveryPage.d.ts.map +1 -1
  78. package/dist/mybodyshield/pages/recovery/RecoveryPage.js +206 -200
  79. package/dist/mybodyshield/pages/recovery/RecoveryPage.js.map +1 -1
  80. package/dist/mybodyshield/pages/train/SessionDetailPage.d.ts.map +1 -1
  81. package/dist/mybodyshield/pages/train/SessionDetailPage.js +327 -294
  82. package/dist/mybodyshield/pages/train/SessionDetailPage.js.map +1 -1
  83. package/dist/mybodyshield/pages/train/SessionsPage.d.ts.map +1 -1
  84. package/dist/mybodyshield/pages/train/SessionsPage.js +180 -163
  85. package/dist/mybodyshield/pages/train/SessionsPage.js.map +1 -1
  86. package/dist/mybodyshield/utils/i18n.d.ts +1 -2
  87. package/dist/mybodyshield/utils/i18n.d.ts.map +1 -1
  88. package/dist/mybodyshield/utils/i18n.js +15 -0
  89. package/dist/mybodyshield/utils/i18n.js.map +1 -0
  90. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/mybodyshield/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard — MyBodyShield health & fitness overview.\n * @module mybodyshield/pages\n *\n * KPIs are aggregated client-side from the real `myb*` list endpoints:\n * active programs, this-week's sessions, goal progress, today's macros vs\n * target, latest body weight and recovery readiness. No mock data.\n */\n\nimport { useMemo } from 'react';\nimport type { ReactNode } from 'react';\nimport { motion, useReducedMotion } from 'framer-motion';\nimport {\n Dumbbell,\n Flame,\n Target,\n Salad,\n Moon,\n ArrowRight,\n CheckCircle2,\n Weight,\n CalendarCheck,\n Sparkles,\n BedDouble,\n TrendingUp,\n Utensils,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { MiniBar } from '../components/MiniBar';\nimport { LoadingView, Section } from '../components/StateViews';\nimport { EmptyState } from '../components/EmptyState';\nimport {\n Meter,\n ProgressRing,\n MacroBar,\n MacroLegend,\n IconChip,\n GOAL_TYPE_META,\n RECOVERY_META,\n WORKOUT_TYPE_META,\n computeGoalProgress,\n} from './_shared/visuals';\nimport { useMyBodyShield } from '../context';\nimport {\n useMybPrograms,\n useMybSessions,\n useMybGoals,\n useMybDailyMacros,\n useMybBodyMetrics,\n useMybRecoveryLogs,\n useMybTodayGuidance,\n} from '../hooks';\nimport type { MybGuidanceAction } from '../types';\n\nimport {\n formatNumber,\n formatRelativeTime,\n formatKcal,\n formatWeight,\n formatPercent,\n formatEnumLabel,\n} from '../utils/formatters';\n\n/** Shared easing for entrance / hover motion (smooth ease-out). */\nconst EASE_OUT: [number, number, number, number] = [0.16, 1, 0.3, 1];\n\n/** Icon + status-token accent per guidance action (BOFF-4174). */\nconst GUIDANCE_ACTION_META: Record<\n MybGuidanceAction,\n { icon: typeof Moon; textClass: string; bgClass: string; label: string }\n> = {\n REST: {\n icon: BedDouble,\n textClass: 'text-status-error-text',\n bgClass: 'bg-status-error-bg-subtle',\n label: 'Rest',\n },\n LIGHTEN_SESSION: {\n icon: Moon,\n textClass: 'text-status-warning-text',\n bgClass: 'bg-status-warning-bg-subtle',\n label: 'Lighten session',\n },\n PROCEED_AS_PLANNED: {\n icon: CheckCircle2,\n textClass: 'text-status-success-text',\n bgClass: 'bg-status-success-bg-subtle',\n label: 'Proceed as planned',\n },\n INCREASE_LOAD: {\n icon: TrendingUp,\n textClass: 'text-status-success-text',\n bgClass: 'bg-status-success-bg-subtle',\n label: 'Increase load',\n },\n REVIEW_NUTRITION: {\n icon: Utensils,\n textClass: 'text-status-warning-text',\n bgClass: 'bg-status-warning-bg-subtle',\n label: 'Review nutrition',\n },\n};\n\nexport function DashboardPage() {\n const { navigateTo, currentUser } = useMyBodyShield();\n const reduceMotion = useReducedMotion();\n\n /** Staggered fade/rise entrance — one-shot, skipped under reduced motion. */\n const rise = (i: number) =>\n reduceMotion\n ? {}\n : {\n initial: { opacity: 0, y: 14 },\n whileInView: { opacity: 1, y: 0 },\n viewport: { once: true, amount: 0.15 },\n transition: { duration: 0.45, delay: Math.min(i, 8) * 0.05, ease: EASE_OUT },\n };\n\n /** Gentle hover lift for cards. */\n const lift = reduceMotion\n ? {}\n : { whileHover: { y: -3, transition: { duration: 0.2, ease: EASE_OUT } } };\n\n /** Hover lift + tap feedback for interactive cards. */\n const liftTap = reduceMotion\n ? {}\n : {\n whileHover: { y: -3, transition: { duration: 0.2, ease: EASE_OUT } },\n whileTap: { scale: 0.985 },\n };\n\n const programs = useMybPrograms({ status: 'ACTIVE', pagination: { limit: 50 } });\n const sessions = useMybSessions({ pagination: { limit: 20 } });\n const goals = useMybGoals({ status: 'ACTIVE', pagination: { limit: 50 } });\n const macros = useMybDailyMacros();\n const weightMetrics = useMybBodyMetrics({ type: 'WEIGHT', pagination: { limit: 30 } });\n const readiness = useMybRecoveryLogs('READINESS');\n // BOFF-4174: recovery-aware adaptive guidance — deterministic rule-based\n // cards, always available; `aiSummary` is an optional richer add-on.\n const guidance = useMybTodayGuidance();\n\n const programRows = programs.data?.items ?? [];\n const sessionRows = sessions.data?.items ?? [];\n const goalRows = goals.data?.items ?? [];\n const weightRows = weightMetrics.data?.items ?? [];\n const readinessRows = readiness.data?.items ?? [];\n\n const now = new Date();\n const weekAgo = now.getTime() - 7 * 24 * 60 * 60 * 1000;\n const sessionsThisWeek = sessionRows.filter((s) => {\n const ref = s.completedAt ?? s.startedAt ?? s.scheduledFor ?? s.createdAt;\n return ref ? new Date(ref).getTime() >= weekAgo : false;\n });\n const completedThisWeek = sessionsThisWeek.filter((s) => s.status === 'COMPLETED').length;\n const activeSession = sessionRows.find((s) => s.status === 'IN_PROGRESS');\n const upcomingSession = sessionRows\n .filter((s) => s.status === 'SCHEDULED' && s.scheduledFor)\n .sort((a, b) => new Date(a.scheduledFor!).getTime() - new Date(b.scheduledFor!).getTime())[0];\n\n const latestWeight = weightRows[0];\n const priorWeight = weightRows[1];\n const weightDelta =\n latestWeight && priorWeight ? latestWeight.value - priorWeight.value : undefined;\n\n const latestReadiness = readinessRows\n .slice()\n .sort((a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime())[0];\n\n const goalProgressAvg = useMemo(() => {\n if (goalRows.length === 0) return 0;\n const sum = goalRows.reduce((acc, g) => acc + computeGoalProgress(g), 0);\n return sum / goalRows.length;\n }, [goalRows]);\n\n const macroData = macros.data;\n const caloriePct = macroData?.calorieTarget\n ? (macroData.calories / macroData.calorieTarget) * 100\n : 0;\n\n const isInitialLoading =\n programs.isLoading && sessions.isLoading && goals.isLoading && macros.isLoading;\n\n if (isInitialLoading) {\n return (\n <PageLayout title=\"MyBodyShield\" titleGradient aurora>\n <LoadingView label=\"Loading your fitness overview…\" />\n </PageLayout>\n );\n }\n\n const greetingName = currentUser?.firstName || currentUser?.name || 'there';\n\n const kpiCards: { id: string; node: ReactNode }[] = [\n {\n id: 'programs',\n node: (\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={formatNumber(programRows.length)}\n label=\"Active programs\"\n footer={\n <p className=\"text-xs text-text-tertiary\">\n <button\n type=\"button\"\n onClick={() => navigateTo('programs')}\n className=\"font-medium text-accent hover:underline\"\n >\n Manage programs\n </button>\n </p>\n }\n />\n ),\n },\n {\n id: 'sessions',\n node: (\n <MetricsCard\n icon={<Flame className=\"h-5 w-5\" />}\n value={formatNumber(completedThisWeek)}\n label=\"Sessions this week\"\n accent={activeSession ? 'warning' : undefined}\n footer={\n activeSession ? (\n <button\n type=\"button\"\n onClick={() => navigateTo(`train/${activeSession.id}`)}\n className=\"text-xs font-medium text-status-warning-text hover:underline\"\n >\n Resume live session →\n </button>\n ) : (\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{sessionsThisWeek.length}</span>{' '}\n logged\n </p>\n )\n }\n />\n ),\n },\n {\n id: 'goals',\n node: (\n <MetricsCard\n icon={<Target className=\"h-5 w-5\" />}\n value={formatNumber(goalRows.length)}\n label=\"Active goals\"\n footer={\n <div className=\"space-y-2\">\n <MiniBar value={goalProgressAvg} height={4} barClassName=\"bg-action-primary-bg\" />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">\n {formatPercent(goalProgressAvg)}\n </span>{' '}\n avg progress\n </p>\n </div>\n }\n />\n ),\n },\n {\n id: 'weight',\n node: (\n <MetricsCard\n icon={<Weight className=\"h-5 w-5\" />}\n value={latestWeight ? formatWeight(latestWeight.value) : '—'}\n label=\"Latest weight\"\n footer={\n weightDelta != null ? (\n <p className=\"text-xs text-text-tertiary\">\n <span\n className={\n weightDelta <= 0\n ? 'font-medium text-status-success-text'\n : 'font-medium text-text-secondary'\n }\n >\n {weightDelta > 0 ? '+' : ''}\n {weightDelta.toFixed(1)} kg\n </span>{' '}\n vs last\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => navigateTo('progress')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log a measurement →\n </button>\n )\n }\n />\n ),\n },\n {\n id: 'recovery',\n node: (\n <MetricsCard\n icon={<Moon className=\"h-5 w-5\" />}\n value={latestReadiness ? `${latestReadiness.score}` : '—'}\n label=\"Recovery readiness\"\n accent={\n latestReadiness\n ? latestReadiness.score >= 70\n ? 'success'\n : latestReadiness.score >= 40\n ? 'warning'\n : 'error'\n : undefined\n }\n footer={\n latestReadiness ? (\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(latestReadiness.loggedAt)}\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => navigateTo('recovery')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log recovery →\n </button>\n )\n }\n />\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"MyBodyShield\"\n titleGradient\n aurora\n description={`Welcome back, ${greetingName} — your training, nutrition, progress & recovery control room.`}\n >\n {/* KPI grid */}\n <div\n data-tour=\"mybodyshield-dashboard-kpi-grid\"\n className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5\"\n >\n {kpiCards.map((card, i) => (\n <motion.div\n key={card.id}\n className=\"group relative rounded-2xl transition-shadow duration-300 hover:shadow-float\"\n {...rise(i)}\n {...lift}\n >\n {/* Emerald top-edge accent — brightens on hover */}\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-x-6 top-0 z-10 h-px rounded-full bg-gradient-to-r from-transparent via-action-primary-bg to-transparent opacity-40 transition-opacity duration-300 group-hover:opacity-90\"\n />\n {card.node}\n </motion.div>\n ))}\n </div>\n\n {/* BOFF-4174: recovery-aware adaptive guidance. Only rendered once\n there's something to say (loading finished + at least one card),\n so a brand-new workspace with zero logged data doesn't get an\n empty/loading box competing with the KPI grid above. */}\n {!guidance.isLoading && (guidance.data?.cards.length ?? 0) > 0 && (\n <motion.div className=\"mt-6\" {...rise(0)}>\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"mb-4 flex items-center gap-2\">\n <IconChip\n icon={<Sparkles className=\"h-5 w-5\" />}\n tint=\"var(--color-accent-primary)\"\n />\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Today&apos;s guidance</h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Recovery-aware suggestions from your logged training &amp; recovery data.\n </p>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {guidance.data?.cards.map((card, i) => {\n const meta = GUIDANCE_ACTION_META[card.action];\n const Icon = meta.icon;\n return (\n <div\n key={`${card.signal}-${i}`}\n className={`rounded-xl border border-seam ${meta.bgClass} p-4`}\n >\n <div className=\"mb-1.5 flex items-center gap-1.5\">\n <Icon className={`h-4 w-4 shrink-0 ${meta.textClass}`} />\n <span\n className={`text-xs font-semibold uppercase tracking-wide ${meta.textClass}`}\n >\n {meta.label}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary\">{card.reason}</p>\n </div>\n );\n })}\n </div>\n {guidance.data?.aiSummary && (\n <p className=\"mt-4 rounded-lg bg-surface p-3 text-sm italic text-text-secondary\">\n {guidance.data.aiSummary}\n </p>\n )}\n </GlassCard>\n </motion.div>\n )}\n\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Today's nutrition */}\n <motion.div\n data-tour=\"mybodyshield-dashboard-nutrition\"\n className=\"lg:col-span-2\"\n {...rise(0)}\n >\n <GlassCard treatment=\"glass\" className=\"h-full p-6\">\n <div className=\"mb-4 flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">\n Today&apos;s nutrition\n </h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Calories & macros logged against your active plan.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo('nutrition')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n Nutrition <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n {!macroData || (macroData.calorieTarget === 0 && macroData.calories === 0) ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Salad className=\"h-10 w-10\" />}\n title=\"No nutrition data yet\"\n description=\"Activate a nutrition plan and log meals to track your daily macros here.\"\n />\n ) : (\n <div className=\"flex flex-col gap-8 sm:flex-row sm:items-center\">\n {/* Calorie ring — the nutrition hero */}\n <div className=\"relative mx-auto flex shrink-0 items-center justify-center sm:mx-0\">\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-2 rounded-full opacity-70 blur-2xl\"\n style={{\n background:\n 'radial-gradient(circle, color-mix(in srgb, var(--color-action-primary-bg) 26%, transparent), transparent 70%)',\n }}\n />\n <ProgressRing value={caloriePct} size={188} stroke={16}>\n <span className=\"text-[11px] font-medium uppercase tracking-wide text-text-tertiary\">\n Calories\n </span>\n <span className=\"text-3xl font-bold tabular-nums text-text-primary\">\n {formatNumber(macroData.calories)}\n </span>\n <span className=\"mt-0.5 text-xs text-text-tertiary\">\n / {formatNumber(macroData.calorieTarget)} kcal\n </span>\n </ProgressRing>\n </div>\n <div className=\"flex-1 space-y-4\">\n <div className=\"space-y-2\">\n <MacroBar\n proteinG={macroData.proteinG}\n carbsG={macroData.carbsG}\n fatG={macroData.fatG}\n height={12}\n />\n <MacroLegend />\n </div>\n <div className=\"grid grid-cols-3 gap-3\">\n {(\n [\n ['Protein', macroData.proteinG, macroData.proteinTarget],\n ['Carbs', macroData.carbsG, macroData.carbsTarget],\n ['Fat', macroData.fatG, macroData.fatTarget],\n ] as const\n ).map(([label, val, target]) => {\n const pct = target ? (val / target) * 100 : 0;\n return (\n <div\n key={label}\n className=\"rounded-2xl bg-bg-sunken/60 p-3 ring-1 ring-inset ring-border-seam/40 transition-shadow hover:shadow-float\"\n >\n <div className=\"flex items-center justify-between gap-1\">\n <p className=\"text-xs font-medium text-text-tertiary\">{label}</p>\n <span className=\"text-[11px] tabular-nums text-text-tertiary\">\n {formatPercent(pct)}\n </span>\n </div>\n <p className=\"mt-1 text-sm font-semibold tabular-nums text-text-primary\">\n {formatNumber(val)}\n <span className=\"text-xs font-normal text-text-tertiary\">\n {' '}\n / {formatNumber(target)} g\n </span>\n </p>\n <Meter value={pct} height={5} className=\"mt-2\" />\n </div>\n );\n })}\n </div>\n </div>\n </div>\n )}\n </GlassCard>\n </motion.div>\n\n {/* Next up */}\n <motion.div data-tour=\"mybodyshield-dashboard-next-up\" {...rise(1)}>\n <GlassCard treatment=\"glass\" className=\"h-full p-6\">\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <h2 className=\"text-base font-semibold text-text-primary\">Next up</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('train')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n Train <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n {activeSession ? (\n <motion.button\n type=\"button\"\n onClick={() => navigateTo(`train/${activeSession.id}`)}\n className=\"w-full rounded-2xl border border-status-warning-text/40 bg-status-warning-bg-subtle p-4 text-left transition-shadow hover:shadow-float\"\n {...liftTap}\n >\n <div className=\"flex items-center gap-2\">\n <StatusBadge status={activeSession.status} />\n </div>\n <p className=\"mt-2 text-sm font-medium text-text-primary\">\n Live workout in progress\n </p>\n <p className=\"mt-0.5 text-xs text-text-tertiary\">\n Started {formatRelativeTime(activeSession.startedAt)}\n </p>\n </motion.button>\n ) : upcomingSession ? (\n <motion.button\n type=\"button\"\n onClick={() => navigateTo(`train/${upcomingSession.id}`)}\n className=\"w-full rounded-2xl bg-bg-sunken/60 p-4 text-left ring-1 ring-inset ring-border-seam/40 transition-shadow hover:shadow-float\"\n {...liftTap}\n >\n <div className=\"flex items-center gap-2\">\n <IconChip icon={<CalendarCheck className=\"h-4 w-4\" />} />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">Scheduled session</p>\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(upcomingSession.scheduledFor)}\n </p>\n </div>\n </div>\n </motion.button>\n ) : (\n <div className=\"flex flex-col items-center justify-center rounded-2xl border border-dashed border-border-seam py-8 text-center\">\n <span className=\"flex size-11 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Flame className=\"h-5 w-5 text-accent\" />\n </span>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">No session scheduled</p>\n <button\n type=\"button\"\n onClick={() => navigateTo('train')}\n className=\"mt-1 text-xs font-medium text-accent hover:underline\"\n >\n Start a workout\n </button>\n </div>\n )}\n </GlassCard>\n </motion.div>\n </div>\n\n {/* Active goals */}\n <Section\n data-tour=\"mybodyshield-dashboard-active-goals\"\n title=\"Active goals\"\n description=\"Your current targets across training, nutrition and body composition.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('goals')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n All goals <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {goalRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Target className=\"h-10 w-10\" />}\n title=\"No active goals\"\n description=\"Set a goal to give your training and nutrition a clear target to work toward.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {goalRows.slice(0, 6).map((g, i) => {\n const pct = computeGoalProgress(g);\n const meta = GOAL_TYPE_META[g.type];\n const Icon = meta.icon;\n return (\n <motion.button\n key={g.id}\n type=\"button\"\n onClick={() => navigateTo('goals')}\n className=\"text-left\"\n {...rise(i)}\n {...liftTap}\n >\n <GlassCard\n treatment=\"plain\"\n className=\"h-full p-4 transition-shadow hover:shadow-float\"\n >\n <div className=\"flex items-start gap-3\">\n <IconChip icon={<Icon className=\"h-4 w-4\" />} tint={meta.tint} />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {g.title}\n </p>\n <p className=\"text-xs text-text-tertiary\">{formatEnumLabel(g.type)}</p>\n </div>\n </div>\n <div className=\"mt-3 space-y-1.5\">\n <Meter value={pct} />\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(g.currentValue)}\n {g.unit ? ` ${g.unit}` : ''} / {formatNumber(g.targetValue)}\n {g.unit ? ` ${g.unit}` : ''}\n </span>\n <span className=\"tabular-nums text-text-tertiary\">\n {formatPercent(pct)}\n </span>\n </div>\n </div>\n </GlassCard>\n </motion.button>\n );\n })}\n </div>\n )}\n </Section>\n\n {/* Recent sessions */}\n <Section\n data-tour=\"mybodyshield-dashboard-recent-sessions\"\n title=\"Recent sessions\"\n description=\"Your latest logged and scheduled workouts.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('train')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n All sessions <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {sessionRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Flame className=\"h-10 w-10\" />}\n title=\"No sessions yet\"\n description=\"Start a workout session to begin logging sets and tracking your training.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {sessionRows.slice(0, 6).map((s, i) => {\n const meta = WORKOUT_TYPE_META.STRENGTH;\n const Icon = s.status === 'COMPLETED' ? CheckCircle2 : meta.icon;\n return (\n <motion.button\n key={s.id}\n type=\"button\"\n onClick={() => navigateTo(`train/${s.id}`)}\n className=\"text-left\"\n {...rise(i)}\n {...liftTap}\n >\n <GlassCard treatment=\"plain\" className=\"p-4 transition-shadow hover:shadow-float\">\n <div className=\"flex items-center justify-between gap-2\">\n <IconChip icon={<Icon className=\"h-4 w-4\" />} tint={meta.tint} />\n <StatusBadge status={s.status} />\n </div>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">\n {s.caloriesBurned > 0\n ? `${formatNumber(s.caloriesBurned)} kcal burned`\n : 'Workout session'}\n </p>\n <p className=\"mt-0.5 text-xs text-text-tertiary\">\n {formatRelativeTime(s.completedAt ?? s.startedAt ?? s.createdAt)}\n </p>\n </GlassCard>\n </motion.button>\n );\n })}\n </div>\n )}\n </Section>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmEA,IAAM,IAA6C;CAAC;CAAM;CAAG;CAAK;CAAE,EAG9D,IAGF;CACF,MAAM;EACJ,MAAM;EACN,WAAW;EACX,SAAS;EACT,OAAO;EACR;CACD,iBAAiB;EACf,MAAM;EACN,WAAW;EACX,SAAS;EACT,OAAO;EACR;CACD,oBAAoB;EAClB,MAAM;EACN,WAAW;EACX,SAAS;EACT,OAAO;EACR;CACD,eAAe;EACb,MAAM;EACN,WAAW;EACX,SAAS;EACT,OAAO;EACR;CACD,kBAAkB;EAChB,MAAM;EACN,WAAW;EACX,SAAS;EACT,OAAO;EACR;CACF;AAED,SAAgB,IAAgB;CAC9B,IAAM,EAAE,eAAY,mBAAgB,GAAiB,EAC/C,IAAe,IAAkB,EAGjC,KAAQ,MACZ,IACI,EAAE,GACF;EACE,SAAS;GAAE,SAAS;GAAG,GAAG;GAAI;EAC9B,aAAa;GAAE,SAAS;GAAG,GAAG;GAAG;EACjC,UAAU;GAAE,MAAM;GAAM,QAAQ;GAAM;EACtC,YAAY;GAAE,UAAU;GAAM,OAAO,KAAK,IAAI,GAAG,EAAE,GAAG;GAAM,MAAM;GAAU;EAC7E,EAGD,IAAO,IACT,EAAE,GACF,EAAE,YAAY;EAAE,GAAG;EAAI,YAAY;GAAE,UAAU;GAAK,MAAM;GAAU;EAAE,EAAE,EAGtE,IAAU,IACZ,EAAE,GACF;EACE,YAAY;GAAE,GAAG;GAAI,YAAY;IAAE,UAAU;IAAK,MAAM;IAAU;GAAE;EACpE,UAAU,EAAE,OAAO,MAAO;EAC3B,EAEC,IAAW,GAAe;EAAE,QAAQ;EAAU,YAAY,EAAE,OAAO,IAAI;EAAE,CAAC,EAC1E,IAAW,EAAe,EAAE,YAAY,EAAE,OAAO,IAAI,EAAE,CAAC,EACxD,IAAQ,GAAY;EAAE,QAAQ;EAAU,YAAY,EAAE,OAAO,IAAI;EAAE,CAAC,EACpE,IAAS,IAAmB,EAC5B,KAAgB,GAAkB;EAAE,MAAM;EAAU,YAAY,EAAE,OAAO,IAAI;EAAE,CAAC,EAChF,KAAY,GAAmB,YAAY,EAG3C,IAAW,GAAqB,EAEhC,KAAc,EAAS,MAAM,SAAS,EAAE,EACxC,IAAc,EAAS,MAAM,SAAS,EAAE,EACxC,IAAW,EAAM,MAAM,SAAS,EAAE,EAClC,IAAa,GAAc,MAAM,SAAS,EAAE,EAC5C,KAAgB,GAAU,MAAM,SAAS,EAAE,EAG3C,sBADM,IAAI,MAAM,EACF,SAAS,GAAG,QAAc,KAAK,KAC7C,IAAmB,EAAY,QAAQ,MAAM;EACjD,IAAM,IAAM,EAAE,eAAe,EAAE,aAAa,EAAE,gBAAgB,EAAE;AAChE,SAAO,IAAM,IAAI,KAAK,EAAI,CAAC,SAAS,IAAI,KAAU;GAClD,EACI,IAAoB,EAAiB,QAAQ,MAAM,EAAE,WAAW,YAAY,CAAC,QAC7E,IAAgB,EAAY,MAAM,MAAM,EAAE,WAAW,cAAc,EACnE,IAAkB,EACrB,QAAQ,MAAM,EAAE,WAAW,eAAe,EAAE,aAAa,CACzD,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,aAAc,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,aAAc,CAAC,SAAS,CAAC,CAAC,IAEvF,IAAe,EAAW,IAC1B,IAAc,EAAW,IACzB,IACJ,KAAgB,IAAc,EAAa,QAAQ,EAAY,QAAQ,KAAA,GAEnE,IAAkB,GACrB,OAAO,CACP,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC,IAE7E,IAAkB,SAClB,EAAS,WAAW,IAAU,IACtB,EAAS,QAAQ,GAAK,MAAM,IAAM,EAAoB,EAAE,EAAE,EAAE,GAC3D,EAAS,QACrB,CAAC,EAAS,CAAC,EAER,IAAY,EAAO,MACnB,KAAa,GAAW,gBACzB,EAAU,WAAW,EAAU,gBAAiB,MACjD;AAKJ,KAFE,EAAS,aAAa,EAAS,aAAa,EAAM,aAAa,EAAO,UAGtE,QACE,kBAAC,GAAD;EAAY,OAAM;EAAe,eAAA;EAAc,QAAA;YAC7C,kBAAC,IAAD,EAAa,OAAM,kCAAmC,CAAA;EAC3C,CAAA;CAIjB,IAAM,KAAe,GAAa,aAAa,GAAa,QAAQ,SAE9D,KAA8C;EAClD;GACE,IAAI;GACJ,MACE,kBAAC,GAAD;IACE,MAAM,kBAAC,IAAD,EAAU,WAAU,WAAY,CAAA;IACtC,OAAO,EAAa,GAAY,OAAO;IACvC,OAAM;IACN,QACE,kBAAC,KAAD;KAAG,WAAU;eACX,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAW,WAAW;MACrC,WAAU;gBACX;MAEQ,CAAA;KACP,CAAA;IAEN,CAAA;GAEL;EACD;GACE,IAAI;GACJ,MACE,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;IACnC,OAAO,EAAa,EAAkB;IACtC,OAAM;IACN,QAAQ,IAAgB,YAAY,KAAA;IACpC,QACE,IACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,SAAS,EAAc,KAAK;KACtD,WAAU;eACX;KAEQ,CAAA,GAET,kBAAC,KAAD;KAAG,WAAU;eAAb;MACE,kBAAC,QAAD;OAAM,WAAU;iBAAoC,EAAiB;OAAc,CAAA;MAAC;MAAI;MAEtF;;IAGR,CAAA;GAEL;EACD;GACE,IAAI;GACJ,MACE,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;IACpC,OAAO,EAAa,EAAS,OAAO;IACpC,OAAM;IACN,QACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAS,OAAO;MAAiB,QAAQ;MAAG,cAAa;MAAyB,CAAA,EAClF,kBAAC,KAAD;MAAG,WAAU;gBAAb;OACE,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAc,EAAgB;QAC1B,CAAA;OAAC;OAAI;OAEV;QACA;;IAER,CAAA;GAEL;EACD;GACE,IAAI;GACJ,MACE,kBAAC,GAAD;IACE,MAAM,kBAAC,IAAD,EAAQ,WAAU,WAAY,CAAA;IACpC,OAAO,IAAe,EAAa,EAAa,MAAM,GAAG;IACzD,OAAM;IACN,QACE,KAAe,OAeb,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,WAAW;KACrC,WAAU;eACX;KAEQ,CAAA,GApBT,kBAAC,KAAD;KAAG,WAAU;eAAb;MACE,kBAAC,QAAD;OACE,WACE,KAAe,IACX,yCACA;iBAJR;QAOG,IAAc,IAAI,MAAM;QACxB,EAAY,QAAQ,EAAE;QAAC;QACnB;;MAAC;MAAI;MAEV;;IAWR,CAAA;GAEL;EACD;GACE,IAAI;GACJ,MACE,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;IAClC,OAAO,IAAkB,GAAG,EAAgB,UAAU;IACtD,OAAM;IACN,QACE,IACI,EAAgB,SAAS,KACvB,YACA,EAAgB,SAAS,KACvB,YACA,UACJ,KAAA;IAEN,QACE,IACE,kBAAC,KAAD;KAAG,WAAU;eACV,EAAmB,EAAgB,SAAS;KAC3C,CAAA,GAEJ,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,WAAW;KACrC,WAAU;eACX;KAEQ,CAAA;IAGb,CAAA;GAEL;EACF;AAED,QACE,kBAAC,GAAD;EACE,OAAM;EACN,eAAA;EACA,QAAA;EACA,aAAa,iBAAiB,GAAa;YAJ7C;GAOE,kBAAC,OAAD;IACE,aAAU;IACV,WAAU;cAET,GAAS,KAAK,GAAM,MACnB,kBAAC,EAAO,KAAR;KAEE,WAAU;KACV,GAAI,EAAK,EAAE;KACX,GAAI;eAJN,CAOE,kBAAC,QAAD;MACE,eAAA;MACA,WAAU;MACV,CAAA,EACD,EAAK,KACK;OAXN,EAAK,GAWC,CACb;IACE,CAAA;GAML,CAAC,EAAS,cAAc,EAAS,MAAM,MAAM,UAAU,KAAK,KAC3D,kBAAC,EAAO,KAAR;IAAY,WAAU;IAAO,GAAI,EAAK,EAAE;cACtC,kBAAC,GAAD;KAAW,WAAU;KAAQ,WAAU;eAAvC;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QACE,MAAM,kBAAC,IAAD,EAAU,WAAU,WAAY,CAAA;QACtC,MAAK;QACL,CAAA,EACF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;kBAA4C;QAA0B,CAAA,EACpF,kBAAC,KAAD;QAAG,WAAU;kBAAqC;QAE9C,CAAA,CACA,EAAA,CAAA,CACF;;MACN,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAS,MAAM,MAAM,KAAK,GAAM,MAAM;QACrC,IAAM,IAAO,EAAqB,EAAK,SACjC,IAAO,EAAK;AAClB,eACE,kBAAC,OAAD;SAEE,WAAW,iCAAiC,EAAK,QAAQ;mBAF3D,CAIE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD,EAAM,WAAW,oBAAoB,EAAK,aAAe,CAAA,EACzD,kBAAC,QAAD;WACE,WAAW,iDAAiD,EAAK;qBAEhE,EAAK;WACD,CAAA,CACH;aACN,kBAAC,KAAD;UAAG,WAAU;oBAA+B,EAAK;UAAW,CAAA,CACxD;WAZC,GAAG,EAAK,OAAO,GAAG,IAYnB;SAER;OACE,CAAA;MACL,EAAS,MAAM,aACd,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAS,KAAK;OACb,CAAA;MAEI;;IACD,CAAA;GAGf,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,EAAO,KAAR;KACE,aAAU;KACV,WAAU;KACV,GAAI,EAAK,EAAE;eAEX,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;kBAA4C;QAErD,CAAA,EACL,kBAAC,KAAD;QAAG,WAAU;kBAAqC;QAE9C,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAW,YAAY;QACtC,WAAU;kBAHZ,CAIC,cACW,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,CACrC;UACL;UACL,CAAC,KAAc,EAAU,kBAAkB,KAAK,EAAU,aAAa,IACtE,kBAAC,GAAD;OACE,cAAa;OACb,MAAM,kBAAC,GAAD,EAAO,WAAU,aAAc,CAAA;OACrC,OAAM;OACN,aAAY;OACZ,CAAA,GAEF,kBAAC,OAAD;OAAK,WAAU;iBAAf,CAEE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SACE,eAAA;SACA,WAAU;SACV,OAAO,EACL,YACE,iHACH;SACD,CAAA,EACF,kBAAC,IAAD;SAAc,OAAO;SAAY,MAAM;SAAK,QAAQ;mBAApD;UACE,kBAAC,QAAD;WAAM,WAAU;qBAAqE;WAE9E,CAAA;UACP,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAa,EAAU,SAAS;WAC5B,CAAA;UACP,kBAAC,QAAD;WAAM,WAAU;qBAAhB;YAAoD;YAC/C,EAAa,EAAU,cAAc;YAAC;YACpC;;UACM;WACX;WACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,IAAD;UACE,UAAU,EAAU;UACpB,QAAQ,EAAU;UAClB,MAAM,EAAU;UAChB,QAAQ;UACR,CAAA,EACF,kBAAC,IAAD,EAAe,CAAA,CACX;YACN,kBAAC,OAAD;SAAK,WAAU;mBAEX;UACE;WAAC;WAAW,EAAU;WAAU,EAAU;WAAc;UACxD;WAAC;WAAS,EAAU;WAAQ,EAAU;WAAY;UAClD;WAAC;WAAO,EAAU;WAAM,EAAU;WAAU;UAC7C,CACD,KAAK,CAAC,GAAO,GAAK,OAAY;UAC9B,IAAM,IAAM,IAAU,IAAM,IAAU,MAAM;AAC5C,iBACE,kBAAC,OAAD;WAEE,WAAU;qBAFZ;YAIE,kBAAC,OAAD;aAAK,WAAU;uBAAf,CACE,kBAAC,KAAD;cAAG,WAAU;wBAA0C;cAAU,CAAA,EACjE,kBAAC,QAAD;cAAM,WAAU;wBACb,EAAc,EAAI;cACd,CAAA,CACH;;YACN,kBAAC,KAAD;aAAG,WAAU;uBAAb,CACG,EAAa,EAAI,EAClB,kBAAC,QAAD;cAAM,WAAU;wBAAhB;eACG;eAAI;eACF,EAAa,EAAO;eAAC;eACnB;gBACL;;YACJ,kBAAC,GAAD;aAAO,OAAO;aAAK,QAAQ;aAAG,WAAU;aAAS,CAAA;YAC7C;aAjBC,EAiBD;WAER;SACE,CAAA,CACF;UACF;SAEE;;KACD,CAAA,EAGb,kBAAC,EAAO,KAAR;KAAY,aAAU;KAAiC,GAAI,EAAK,EAAE;eAChE,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBAA4C;QAAY,CAAA,EACtE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAW,QAAQ;QAClC,WAAU;kBAHZ,CAIC,UACO,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,CACjC;UACL;UACL,IACC,kBAAC,EAAO,QAAR;OACE,MAAK;OACL,eAAe,EAAW,SAAS,EAAc,KAAK;OACtD,WAAU;OACV,GAAI;iBAJN;QAME,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAa,QAAQ,EAAc,QAAU,CAAA;SACzC,CAAA;QACN,kBAAC,KAAD;SAAG,WAAU;mBAA6C;SAEtD,CAAA;QACJ,kBAAC,KAAD;SAAG,WAAU;mBAAb,CAAiD,YACtC,EAAmB,EAAc,UAAU,CAClD;;QACU;WACd,IACF,kBAAC,EAAO,QAAR;OACE,MAAK;OACL,eAAe,EAAW,SAAS,EAAgB,KAAK;OACxD,WAAU;OACV,GAAI;iBAEJ,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAU,MAAM,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA,EAAI,CAAA,EACzD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAwC;SAAqB,CAAA,EAC1E,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAmB,EAAgB,aAAa;SAC/C,CAAA,CACA,EAAA,CAAA,CACF;;OACQ,CAAA,GAEhB,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,QAAD;SAAM,WAAU;mBACd,kBAAC,GAAD,EAAO,WAAU,uBAAwB,CAAA;SACpC,CAAA;QACP,kBAAC,KAAD;SAAG,WAAU;mBAA6C;SAAwB,CAAA;QAClF,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAW,QAAQ;SAClC,WAAU;mBACX;SAEQ,CAAA;QACL;SAEE;;KACD,CAAA,CACT;;GAGN,kBAAC,GAAD;IACE,aAAU;IACV,OAAM;IACN,aAAY;IACZ,QACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,QAAQ;KAClC,WAAU;eAHZ,CAIC,cACW,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,CACrC;;cAGV,EAAS,WAAW,IACnB,kBAAC,GAAD;KACE,cAAa;KACb,MAAM,kBAAC,GAAD,EAAQ,WAAU,aAAc,CAAA;KACtC,OAAM;KACN,aAAY;KACZ,CAAA,GAEF,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAS,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG,MAAM;MAClC,IAAM,IAAM,EAAoB,EAAE,EAC5B,IAAO,GAAe,EAAE,OACxB,IAAO,EAAK;AAClB,aACE,kBAAC,EAAO,QAAR;OAEE,MAAK;OACL,eAAe,EAAW,QAAQ;OAClC,WAAU;OACV,GAAI,EAAK,EAAE;OACX,GAAI;iBAEJ,kBAAC,GAAD;QACE,WAAU;QACV,WAAU;kBAFZ,CAIE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAU,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;UAAE,MAAM,EAAK;UAAQ,CAAA,EACjE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAE;WACD,CAAA,EACJ,kBAAC,KAAD;WAAG,WAAU;qBAA8B,EAAgB,EAAE,KAAK;WAAK,CAAA,CACnE;YACF;YACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD,EAAO,OAAO,GAAO,CAAA,EACrB,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,QAAD;WAAM,WAAU;qBAAhB;YACG,EAAa,EAAE,aAAa;YAC5B,EAAE,OAAO,IAAI,EAAE,SAAS;YAAG;YAAI,EAAa,EAAE,YAAY;YAC1D,EAAE,OAAO,IAAI,EAAE,SAAS;YACpB;cACP,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAc,EAAI;WACd,CAAA,CACH;YACF;WACI;;OACE,EAlCT,EAAE,GAkCO;OAElB;KACE,CAAA;IAEA,CAAA;GAGV,kBAAC,GAAD;IACE,aAAU;IACV,OAAM;IACN,aAAY;IACZ,QACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,QAAQ;KAClC,WAAU;eAHZ,CAIC,iBACc,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,CACxC;;cAGV,EAAY,WAAW,IACtB,kBAAC,GAAD;KACE,cAAa;KACb,MAAM,kBAAC,GAAD,EAAO,WAAU,aAAc,CAAA;KACrC,OAAM;KACN,aAAY;KACZ,CAAA,GAEF,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAY,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG,MAAM;MACrC,IAAM,IAAO,GAAkB,UACzB,IAAO,EAAE,WAAW,cAAc,IAAe,EAAK;AAC5D,aACE,kBAAC,EAAO,QAAR;OAEE,MAAK;OACL,eAAe,EAAW,SAAS,EAAE,KAAK;OAC1C,WAAU;OACV,GAAI,EAAK,EAAE;OACX,GAAI;iBAEJ,kBAAC,GAAD;QAAW,WAAU;QAAQ,WAAU;kBAAvC;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD;WAAU,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;WAAE,MAAM,EAAK;WAAQ,CAAA,EACjE,kBAAC,GAAD,EAAa,QAAQ,EAAE,QAAU,CAAA,CAC7B;;SACN,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAE,iBAAiB,IAChB,GAAG,EAAa,EAAE,eAAe,CAAC,gBAClC;UACF,CAAA;SACJ,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAmB,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU;UAC9D,CAAA;SACM;;OACE,EArBT,EAAE,GAqBO;OAElB;KACE,CAAA;IAEA,CAAA;GACC"}
1
+ {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/mybodyshield/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard — MyBodyShield health & fitness overview.\n * @module mybodyshield/pages\n *\n * KPIs are aggregated client-side from the real `myb*` list endpoints:\n * active programs, this-week's sessions, goal progress, today's macros vs\n * target, latest body weight and recovery readiness. No mock data.\n */\n\nimport { useMemo } from 'react';\nimport type { ReactNode } from 'react';\nimport { motion, useReducedMotion } from 'framer-motion';\nimport {\n Dumbbell,\n Flame,\n Target,\n Salad,\n Moon,\n ArrowRight,\n CheckCircle2,\n Weight,\n CalendarCheck,\n Sparkles,\n BedDouble,\n TrendingUp,\n Utensils,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { MiniBar } from '../components/MiniBar';\nimport { LoadingView, Section } from '../components/StateViews';\nimport { EmptyState } from '../components/EmptyState';\nimport {\n Meter,\n ProgressRing,\n MacroBar,\n MacroLegend,\n IconChip,\n GOAL_TYPE_META,\n RECOVERY_META,\n WORKOUT_TYPE_META,\n computeGoalProgress,\n} from './_shared/visuals';\nimport { useMyBodyShield } from '../context';\nimport {\n useMybPrograms,\n useMybSessions,\n useMybGoals,\n useMybDailyMacros,\n useMybBodyMetrics,\n useMybRecoveryLogs,\n useMybTodayGuidance,\n useTr,\n} from '../hooks';\nimport type { MybGuidanceAction } from '../types';\n\nimport {\n formatNumber,\n formatRelativeTime,\n formatKcal,\n formatWeight,\n formatPercent,\n formatEnumLabel,\n} from '../utils/formatters';\n\n/** Shared easing for entrance / hover motion (smooth ease-out). */\nconst EASE_OUT: [number, number, number, number] = [0.16, 1, 0.3, 1];\n\n/** Icon + status-token accent per guidance action (BOFF-4174). */\nconst GUIDANCE_ACTION_META: Record<\n MybGuidanceAction,\n { icon: typeof Moon; textClass: string; bgClass: string; labelKey: string; labelFallback: string }\n> = {\n REST: {\n icon: BedDouble,\n textClass: 'text-status-error-text',\n bgClass: 'bg-status-error-bg-subtle',\n labelKey: 'mybodyshield.pages.dashboard.rest',\n labelFallback: 'Rest',\n },\n LIGHTEN_SESSION: {\n icon: Moon,\n textClass: 'text-status-warning-text',\n bgClass: 'bg-status-warning-bg-subtle',\n labelKey: 'mybodyshield.pages.dashboard.lightenSession',\n labelFallback: 'Lighten session',\n },\n PROCEED_AS_PLANNED: {\n icon: CheckCircle2,\n textClass: 'text-status-success-text',\n bgClass: 'bg-status-success-bg-subtle',\n labelKey: 'mybodyshield.pages.dashboard.proceedAsPlanned',\n labelFallback: 'Proceed as planned',\n },\n INCREASE_LOAD: {\n icon: TrendingUp,\n textClass: 'text-status-success-text',\n bgClass: 'bg-status-success-bg-subtle',\n labelKey: 'mybodyshield.pages.dashboard.increaseLoad',\n labelFallback: 'Increase load',\n },\n REVIEW_NUTRITION: {\n icon: Utensils,\n textClass: 'text-status-warning-text',\n bgClass: 'bg-status-warning-bg-subtle',\n labelKey: 'mybodyshield.pages.dashboard.reviewNutrition',\n labelFallback: 'Review nutrition',\n },\n};\n\nexport function DashboardPage() {\n const t = useTr();\n const { navigateTo, currentUser } = useMyBodyShield();\n const reduceMotion = useReducedMotion();\n\n /** Staggered fade/rise entrance — one-shot, skipped under reduced motion. */\n const rise = (i: number) =>\n reduceMotion\n ? {}\n : {\n initial: { opacity: 0, y: 14 },\n whileInView: { opacity: 1, y: 0 },\n viewport: { once: true, amount: 0.15 },\n transition: { duration: 0.45, delay: Math.min(i, 8) * 0.05, ease: EASE_OUT },\n };\n\n /** Gentle hover lift for cards. */\n const lift = reduceMotion\n ? {}\n : { whileHover: { y: -3, transition: { duration: 0.2, ease: EASE_OUT } } };\n\n /** Hover lift + tap feedback for interactive cards. */\n const liftTap = reduceMotion\n ? {}\n : {\n whileHover: { y: -3, transition: { duration: 0.2, ease: EASE_OUT } },\n whileTap: { scale: 0.985 },\n };\n\n const programs = useMybPrograms({ status: 'ACTIVE', pagination: { limit: 50 } });\n const sessions = useMybSessions({ pagination: { limit: 20 } });\n const goals = useMybGoals({ status: 'ACTIVE', pagination: { limit: 50 } });\n const macros = useMybDailyMacros();\n const weightMetrics = useMybBodyMetrics({ type: 'WEIGHT', pagination: { limit: 30 } });\n const readiness = useMybRecoveryLogs('READINESS');\n // BOFF-4174: recovery-aware adaptive guidance — deterministic rule-based\n // cards, always available; `aiSummary` is an optional richer add-on.\n const guidance = useMybTodayGuidance();\n\n const programRows = programs.data?.items ?? [];\n const sessionRows = sessions.data?.items ?? [];\n const goalRows = goals.data?.items ?? [];\n const weightRows = weightMetrics.data?.items ?? [];\n const readinessRows = readiness.data?.items ?? [];\n\n const now = new Date();\n const weekAgo = now.getTime() - 7 * 24 * 60 * 60 * 1000;\n const sessionsThisWeek = sessionRows.filter((s) => {\n const ref = s.completedAt ?? s.startedAt ?? s.scheduledFor ?? s.createdAt;\n return ref ? new Date(ref).getTime() >= weekAgo : false;\n });\n const completedThisWeek = sessionsThisWeek.filter((s) => s.status === 'COMPLETED').length;\n const activeSession = sessionRows.find((s) => s.status === 'IN_PROGRESS');\n const upcomingSession = sessionRows\n .filter((s) => s.status === 'SCHEDULED' && s.scheduledFor)\n .sort((a, b) => new Date(a.scheduledFor!).getTime() - new Date(b.scheduledFor!).getTime())[0];\n\n const latestWeight = weightRows[0];\n const priorWeight = weightRows[1];\n const weightDelta =\n latestWeight && priorWeight ? latestWeight.value - priorWeight.value : undefined;\n\n const latestReadiness = readinessRows\n .slice()\n .sort((a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime())[0];\n\n const goalProgressAvg = useMemo(() => {\n if (goalRows.length === 0) return 0;\n const sum = goalRows.reduce((acc, g) => acc + computeGoalProgress(g), 0);\n return sum / goalRows.length;\n }, [goalRows]);\n\n const macroData = macros.data;\n const caloriePct = macroData?.calorieTarget\n ? (macroData.calories / macroData.calorieTarget) * 100\n : 0;\n\n const isInitialLoading =\n programs.isLoading && sessions.isLoading && goals.isLoading && macros.isLoading;\n\n if (isInitialLoading) {\n return (\n <PageLayout\n title={t('mybodyshield.pages.dashboard.title', 'MyBodyShield')}\n titleGradient\n aurora\n >\n <LoadingView\n label={t('mybodyshield.pages.dashboard.loading', 'Loading your fitness overview…')}\n />\n </PageLayout>\n );\n }\n\n const greetingName =\n currentUser?.firstName ||\n currentUser?.name ||\n t('mybodyshield.pages.dashboard.greetingFallback', 'there');\n\n const kpiCards: { id: string; node: ReactNode }[] = [\n {\n id: 'programs',\n node: (\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={formatNumber(programRows.length)}\n label={t('mybodyshield.pages.dashboard.activePrograms', 'Active programs')}\n footer={\n <p className=\"text-xs text-text-tertiary\">\n <button\n type=\"button\"\n onClick={() => navigateTo('programs')}\n className=\"font-medium text-accent hover:underline\"\n >\n {t('mybodyshield.pages.dashboard.managePrograms', 'Manage programs')}\n </button>\n </p>\n }\n />\n ),\n },\n {\n id: 'sessions',\n node: (\n <MetricsCard\n icon={<Flame className=\"h-5 w-5\" />}\n value={formatNumber(completedThisWeek)}\n label={t('mybodyshield.pages.dashboard.sessionsThisWeek', 'Sessions this week')}\n accent={activeSession ? 'warning' : undefined}\n footer={\n activeSession ? (\n <button\n type=\"button\"\n onClick={() => navigateTo(`train/${activeSession.id}`)}\n className=\"text-xs font-medium text-status-warning-text hover:underline\"\n >\n {t('mybodyshield.pages.dashboard.resumeLiveSession', 'Resume live session →')}\n </button>\n ) : (\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{sessionsThisWeek.length}</span>{' '}\n {t('mybodyshield.pages.dashboard.logged', 'logged')}\n </p>\n )\n }\n />\n ),\n },\n {\n id: 'goals',\n node: (\n <MetricsCard\n icon={<Target className=\"h-5 w-5\" />}\n value={formatNumber(goalRows.length)}\n label={t('mybodyshield.pages.dashboard.activeGoals', 'Active goals')}\n footer={\n <div className=\"space-y-2\">\n <MiniBar value={goalProgressAvg} height={4} barClassName=\"bg-action-primary-bg\" />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">\n {formatPercent(goalProgressAvg)}\n </span>{' '}\n {t('mybodyshield.pages.dashboard.avgProgress', 'avg progress')}\n </p>\n </div>\n }\n />\n ),\n },\n {\n id: 'weight',\n node: (\n <MetricsCard\n icon={<Weight className=\"h-5 w-5\" />}\n value={latestWeight ? formatWeight(latestWeight.value) : '—'}\n label={t('mybodyshield.pages.dashboard.latestWeight', 'Latest weight')}\n footer={\n weightDelta != null ? (\n <p className=\"text-xs text-text-tertiary\">\n <span\n className={\n weightDelta <= 0\n ? 'font-medium text-status-success-text'\n : 'font-medium text-text-secondary'\n }\n >\n {t('mybodyshield.pages.dashboard.weightDelta', '{{delta}} kg', {\n delta: `${weightDelta > 0 ? '+' : ''}${weightDelta.toFixed(1)}`,\n })}\n </span>{' '}\n {t('mybodyshield.pages.dashboard.vsLast', 'vs last')}\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => navigateTo('progress')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n {t('mybodyshield.pages.dashboard.logMeasurement', 'Log a measurement →')}\n </button>\n )\n }\n />\n ),\n },\n {\n id: 'recovery',\n node: (\n <MetricsCard\n icon={<Moon className=\"h-5 w-5\" />}\n value={latestReadiness ? `${latestReadiness.score}` : '—'}\n label={t('mybodyshield.pages.dashboard.recoveryReadiness', 'Recovery readiness')}\n accent={\n latestReadiness\n ? latestReadiness.score >= 70\n ? 'success'\n : latestReadiness.score >= 40\n ? 'warning'\n : 'error'\n : undefined\n }\n footer={\n latestReadiness ? (\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(latestReadiness.loggedAt)}\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => navigateTo('recovery')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n {t('mybodyshield.pages.dashboard.logRecovery', 'Log recovery →')}\n </button>\n )\n }\n />\n ),\n },\n ];\n\n return (\n <PageLayout\n title={t('mybodyshield.pages.dashboard.title', 'MyBodyShield')}\n titleGradient\n aurora\n description={t(\n 'mybodyshield.pages.dashboard.description',\n 'Welcome back, {{name}} — your training, nutrition, progress & recovery control room.',\n { name: greetingName }\n )}\n >\n {/* KPI grid */}\n <div\n data-tour=\"mybodyshield-dashboard-kpi-grid\"\n className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5\"\n >\n {kpiCards.map((card, i) => (\n <motion.div\n key={card.id}\n className=\"group relative rounded-2xl transition-shadow duration-300 hover:shadow-float\"\n {...rise(i)}\n {...lift}\n >\n {/* Emerald top-edge accent — brightens on hover */}\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-x-6 top-0 z-10 h-px rounded-full bg-gradient-to-r from-transparent via-action-primary-bg to-transparent opacity-40 transition-opacity duration-300 group-hover:opacity-90\"\n />\n {card.node}\n </motion.div>\n ))}\n </div>\n\n {/* BOFF-4174: recovery-aware adaptive guidance. Only rendered once\n there's something to say (loading finished + at least one card),\n so a brand-new workspace with zero logged data doesn't get an\n empty/loading box competing with the KPI grid above. */}\n {!guidance.isLoading && (guidance.data?.cards.length ?? 0) > 0 && (\n <motion.div className=\"mt-6\" {...rise(0)}>\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"mb-4 flex items-center gap-2\">\n <IconChip\n icon={<Sparkles className=\"h-5 w-5\" />}\n tint=\"var(--color-accent-primary)\"\n />\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">\n {t('mybodyshield.pages.dashboard.todaysGuidance', \"Today's guidance\")}\n </h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {t(\n 'mybodyshield.pages.dashboard.guidanceDescription',\n 'Recovery-aware suggestions from your logged training & recovery data.'\n )}\n </p>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {guidance.data?.cards.map((card, i) => {\n const meta = GUIDANCE_ACTION_META[card.action];\n const Icon = meta.icon;\n return (\n <div\n key={`${card.signal}-${i}`}\n className={`rounded-xl border border-seam ${meta.bgClass} p-4`}\n >\n <div className=\"mb-1.5 flex items-center gap-1.5\">\n <Icon className={`h-4 w-4 shrink-0 ${meta.textClass}`} />\n <span\n className={`text-xs font-semibold uppercase tracking-wide ${meta.textClass}`}\n >\n {t(meta.labelKey, meta.labelFallback)}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary\">{card.reason}</p>\n </div>\n );\n })}\n </div>\n {guidance.data?.aiSummary && (\n <p className=\"mt-4 rounded-lg bg-surface p-3 text-sm italic text-text-secondary\">\n {guidance.data.aiSummary}\n </p>\n )}\n </GlassCard>\n </motion.div>\n )}\n\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Today's nutrition */}\n <motion.div\n data-tour=\"mybodyshield-dashboard-nutrition\"\n className=\"lg:col-span-2\"\n {...rise(0)}\n >\n <GlassCard treatment=\"glass\" className=\"h-full p-6\">\n <div className=\"mb-4 flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">\n {t('mybodyshield.pages.dashboard.todaysNutrition', \"Today's nutrition\")}\n </h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {t(\n 'mybodyshield.pages.dashboard.nutritionDescription',\n 'Calories & macros logged against your active plan.'\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo('nutrition')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n {t('mybodyshield.nav.nutrition', 'Nutrition')} <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n {!macroData || (macroData.calorieTarget === 0 && macroData.calories === 0) ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Salad className=\"h-10 w-10\" />}\n title={t('mybodyshield.pages.dashboard.noNutritionData', 'No nutrition data yet')}\n description={t(\n 'mybodyshield.pages.dashboard.noNutritionDescription',\n 'Activate a nutrition plan and log meals to track your daily macros here.'\n )}\n />\n ) : (\n <div className=\"flex flex-col gap-8 sm:flex-row sm:items-center\">\n {/* Calorie ring — the nutrition hero */}\n <div className=\"relative mx-auto flex shrink-0 items-center justify-center sm:mx-0\">\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-2 rounded-full opacity-70 blur-2xl\"\n style={{\n background:\n 'radial-gradient(circle, color-mix(in srgb, var(--color-action-primary-bg) 26%, transparent), transparent 70%)',\n }}\n />\n <ProgressRing value={caloriePct} size={188} stroke={16}>\n <span className=\"text-[11px] font-medium uppercase tracking-wide text-text-tertiary\">\n {t('mybodyshield.pages.dashboard.calories', 'Calories')}\n </span>\n <span className=\"text-3xl font-bold tabular-nums text-text-primary\">\n {formatNumber(macroData.calories)}\n </span>\n <span className=\"mt-0.5 text-xs text-text-tertiary\">\n / {formatNumber(macroData.calorieTarget)} kcal\n </span>\n </ProgressRing>\n </div>\n <div className=\"flex-1 space-y-4\">\n <div className=\"space-y-2\">\n <MacroBar\n proteinG={macroData.proteinG}\n carbsG={macroData.carbsG}\n fatG={macroData.fatG}\n height={12}\n />\n <MacroLegend />\n </div>\n <div className=\"grid grid-cols-3 gap-3\">\n {(\n [\n [\n 'mybodyshield.pages.dashboard.protein',\n 'Protein',\n macroData.proteinG,\n macroData.proteinTarget,\n ],\n [\n 'mybodyshield.pages.dashboard.carbs',\n 'Carbs',\n macroData.carbsG,\n macroData.carbsTarget,\n ],\n [\n 'mybodyshield.pages.dashboard.fat',\n 'Fat',\n macroData.fatG,\n macroData.fatTarget,\n ],\n ] as const\n ).map(([labelKey, labelFallback, val, target]) => {\n const pct = target ? (val / target) * 100 : 0;\n return (\n <div\n key={labelKey}\n className=\"rounded-2xl bg-bg-sunken/60 p-3 ring-1 ring-inset ring-border-seam/40 transition-shadow hover:shadow-float\"\n >\n <div className=\"flex items-center justify-between gap-1\">\n <p className=\"text-xs font-medium text-text-tertiary\">\n {t(labelKey, labelFallback)}\n </p>\n <span className=\"text-[11px] tabular-nums text-text-tertiary\">\n {formatPercent(pct)}\n </span>\n </div>\n <p className=\"mt-1 text-sm font-semibold tabular-nums text-text-primary\">\n {formatNumber(val)}\n <span className=\"text-xs font-normal text-text-tertiary\">\n {' '}\n / {formatNumber(target)} g\n </span>\n </p>\n <Meter value={pct} height={5} className=\"mt-2\" />\n </div>\n );\n })}\n </div>\n </div>\n </div>\n )}\n </GlassCard>\n </motion.div>\n\n {/* Next up */}\n <motion.div data-tour=\"mybodyshield-dashboard-next-up\" {...rise(1)}>\n <GlassCard treatment=\"glass\" className=\"h-full p-6\">\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n {t('mybodyshield.pages.dashboard.nextUp', 'Next up')}\n </h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('train')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n {t('mybodyshield.nav.train', 'Train')} <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n {activeSession ? (\n <motion.button\n type=\"button\"\n onClick={() => navigateTo(`train/${activeSession.id}`)}\n className=\"w-full rounded-2xl border border-status-warning-text/40 bg-status-warning-bg-subtle p-4 text-left transition-shadow hover:shadow-float\"\n {...liftTap}\n >\n <div className=\"flex items-center gap-2\">\n <StatusBadge status={activeSession.status} />\n </div>\n <p className=\"mt-2 text-sm font-medium text-text-primary\">\n {t(\n 'mybodyshield.pages.dashboard.liveWorkoutInProgress',\n 'Live workout in progress'\n )}\n </p>\n <p className=\"mt-0.5 text-xs text-text-tertiary\">\n {t('mybodyshield.pages.dashboard.startedAgo', 'Started {{time}}', {\n time: formatRelativeTime(activeSession.startedAt),\n })}\n </p>\n </motion.button>\n ) : upcomingSession ? (\n <motion.button\n type=\"button\"\n onClick={() => navigateTo(`train/${upcomingSession.id}`)}\n className=\"w-full rounded-2xl bg-bg-sunken/60 p-4 text-left ring-1 ring-inset ring-border-seam/40 transition-shadow hover:shadow-float\"\n {...liftTap}\n >\n <div className=\"flex items-center gap-2\">\n <IconChip icon={<CalendarCheck className=\"h-4 w-4\" />} />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {t('mybodyshield.pages.dashboard.scheduledSession', 'Scheduled session')}\n </p>\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(upcomingSession.scheduledFor)}\n </p>\n </div>\n </div>\n </motion.button>\n ) : (\n <div className=\"flex flex-col items-center justify-center rounded-2xl border border-dashed border-border-seam py-8 text-center\">\n <span className=\"flex size-11 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Flame className=\"h-5 w-5 text-accent\" />\n </span>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">\n {t('mybodyshield.pages.dashboard.noSessionScheduled', 'No session scheduled')}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo('train')}\n className=\"mt-1 text-xs font-medium text-accent hover:underline\"\n >\n {t('mybodyshield.pages.dashboard.startWorkout', 'Start a workout')}\n </button>\n </div>\n )}\n </GlassCard>\n </motion.div>\n </div>\n\n {/* Active goals */}\n <Section\n data-tour=\"mybodyshield-dashboard-active-goals\"\n title={t('mybodyshield.pages.dashboard.activeGoalsSection', 'Active goals')}\n description={t(\n 'mybodyshield.pages.dashboard.activeGoalsDescription',\n 'Your current targets across training, nutrition and body composition.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('goals')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n {t('mybodyshield.pages.dashboard.allGoals', 'All goals')}{' '}\n <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {goalRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Target className=\"h-10 w-10\" />}\n title={t('mybodyshield.pages.dashboard.noActiveGoals', 'No active goals')}\n description={t(\n 'mybodyshield.pages.dashboard.noActiveGoalsDescription',\n 'Set a goal to give your training and nutrition a clear target to work toward.'\n )}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {goalRows.slice(0, 6).map((g, i) => {\n const pct = computeGoalProgress(g);\n const meta = GOAL_TYPE_META[g.type];\n const Icon = meta.icon;\n return (\n <motion.button\n key={g.id}\n type=\"button\"\n onClick={() => navigateTo('goals')}\n className=\"text-left\"\n {...rise(i)}\n {...liftTap}\n >\n <GlassCard\n treatment=\"plain\"\n className=\"h-full p-4 transition-shadow hover:shadow-float\"\n >\n <div className=\"flex items-start gap-3\">\n <IconChip icon={<Icon className=\"h-4 w-4\" />} tint={meta.tint} />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {g.title}\n </p>\n <p className=\"text-xs text-text-tertiary\">{formatEnumLabel(g.type)}</p>\n </div>\n </div>\n <div className=\"mt-3 space-y-1.5\">\n <Meter value={pct} />\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(g.currentValue)}\n {g.unit ? ` ${g.unit}` : ''} / {formatNumber(g.targetValue)}\n {g.unit ? ` ${g.unit}` : ''}\n </span>\n <span className=\"tabular-nums text-text-tertiary\">\n {formatPercent(pct)}\n </span>\n </div>\n </div>\n </GlassCard>\n </motion.button>\n );\n })}\n </div>\n )}\n </Section>\n\n {/* Recent sessions */}\n <Section\n data-tour=\"mybodyshield-dashboard-recent-sessions\"\n title={t('mybodyshield.pages.dashboard.recentSessions', 'Recent sessions')}\n description={t(\n 'mybodyshield.pages.dashboard.recentSessionsDescription',\n 'Your latest logged and scheduled workouts.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('train')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n {t('mybodyshield.pages.dashboard.allSessions', 'All sessions')}{' '}\n <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {sessionRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Flame className=\"h-10 w-10\" />}\n title={t('mybodyshield.pages.dashboard.noSessions', 'No sessions yet')}\n description={t(\n 'mybodyshield.pages.dashboard.noSessionsDescription',\n 'Start a workout session to begin logging sets and tracking your training.'\n )}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {sessionRows.slice(0, 6).map((s, i) => {\n const meta = WORKOUT_TYPE_META.STRENGTH;\n const Icon = s.status === 'COMPLETED' ? CheckCircle2 : meta.icon;\n return (\n <motion.button\n key={s.id}\n type=\"button\"\n onClick={() => navigateTo(`train/${s.id}`)}\n className=\"text-left\"\n {...rise(i)}\n {...liftTap}\n >\n <GlassCard treatment=\"plain\" className=\"p-4 transition-shadow hover:shadow-float\">\n <div className=\"flex items-center justify-between gap-2\">\n <IconChip icon={<Icon className=\"h-4 w-4\" />} tint={meta.tint} />\n <StatusBadge status={s.status} />\n </div>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">\n {s.caloriesBurned > 0\n ? t('mybodyshield.pages.dashboard.kcalBurned', '{{kcal}} kcal burned', {\n kcal: formatNumber(s.caloriesBurned),\n })\n : t('mybodyshield.pages.dashboard.workoutSession', 'Workout session')}\n </p>\n <p className=\"mt-0.5 text-xs text-text-tertiary\">\n {formatRelativeTime(s.completedAt ?? s.startedAt ?? s.createdAt)}\n </p>\n </GlassCard>\n </motion.button>\n );\n })}\n </div>\n )}\n </Section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"SettingsPage.d.ts","sourceRoot":"","sources":["../../../src/mybodyshield/pages/SettingsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,wBAAgB,YAAY,gCAsG3B"}
1
+ {"version":3,"file":"SettingsPage.d.ts","sourceRoot":"","sources":["../../../src/mybodyshield/pages/SettingsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,wBAAgB,YAAY,gCA0H3B"}
@@ -1,92 +1,83 @@
1
1
  import { PageLayout as e } from "../components/PageLayout.js";
2
- import { ErrorView as t, LoadingView as n, Section as r } from "../components/StateViews.js";
3
- import { useMybAiProcessingConsent as i, useMybSetAiProcessingConsent as a } from "../hooks/useMyBodyShieldApi.js";
2
+ import { useMybAiProcessingConsent as t, useMybSetAiProcessingConsent as n } from "../hooks/useMyBodyShieldApi.js";
3
+ import { useTr as r } from "../hooks/useTr.js";
4
4
  import "../hooks/index.js";
5
- import { IconChip as o } from "./_shared/visuals.js";
6
- import { ExternalLink as s, FileText as c, ShieldCheck as l, Sparkles as u } from "lucide-react";
7
- import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
8
- import { Button as m, GlassCard as h, Switch as g } from "@burdenoff/fe-libs/ui";
5
+ import { ErrorView as i, LoadingView as a, Section as o } from "../components/StateViews.js";
6
+ import { IconChip as s } from "./_shared/visuals.js";
7
+ import { ExternalLink as c, FileText as l, ShieldCheck as u, Sparkles as d } from "lucide-react";
8
+ import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
9
+ import { Button as h, GlassCard as g, Switch as _ } from "@burdenoff/fe-libs/ui";
9
10
  //#region src/mybodyshield/pages/SettingsPage.tsx
10
- function _() {
11
- let _ = i(), v = a();
12
- if (_.isLoading) return /* @__PURE__ */ f(e, {
13
- title: "Settings",
14
- children: /* @__PURE__ */ f(n, { label: "Loading settings…" })
11
+ function v() {
12
+ let v = r(), y = t(), b = n();
13
+ if (y.isLoading) return /* @__PURE__ */ p(e, {
14
+ title: v("mybodyshield.pages.settings.title", "Settings"),
15
+ children: /* @__PURE__ */ p(a, { label: v("mybodyshield.pages.settings.loading", "Loading settings…") })
15
16
  });
16
- if (_.isError) return /* @__PURE__ */ f(e, {
17
- title: "Settings",
18
- children: /* @__PURE__ */ f(t, {
19
- title: "Could not load settings",
20
- message: _.error instanceof Error ? _.error.message : "Unknown error",
21
- onRetry: () => _.refetch()
17
+ if (y.isError) return /* @__PURE__ */ p(e, {
18
+ title: v("mybodyshield.pages.settings.title", "Settings"),
19
+ children: /* @__PURE__ */ p(i, {
20
+ title: v("mybodyshield.pages.settings.errorTitle", "Could not load settings"),
21
+ message: y.error instanceof Error ? y.error.message : v("mybodyshield.common.unknownError", "Unknown error"),
22
+ onRetry: () => y.refetch()
22
23
  })
23
24
  });
24
- let y = _.data?.enabled ?? !1;
25
- return /* @__PURE__ */ f(e, {
26
- title: "Settings",
27
- description: "Manage MyBodyShield preferences and AI-processing consent.",
28
- children: /* @__PURE__ */ f(r, {
29
- title: "AI processing",
30
- description: "Control whether AI can read your health data to generate summaries and suggestions.",
31
- children: /* @__PURE__ */ f(h, {
25
+ let x = y.data?.enabled ?? !1;
26
+ return /* @__PURE__ */ p(e, {
27
+ title: v("mybodyshield.pages.settings.title", "Settings"),
28
+ description: v("mybodyshield.pages.settings.description", "Manage MyBodyShield preferences and AI-processing consent."),
29
+ children: /* @__PURE__ */ p(o, {
30
+ title: v("mybodyshield.pages.settings.aiProcessing", "AI processing"),
31
+ description: v("mybodyshield.pages.settings.aiProcessingDescription", "Control whether AI can read your health data to generate summaries and suggestions."),
32
+ children: /* @__PURE__ */ p(g, {
32
33
  treatment: "glass",
33
34
  className: "p-5",
34
- children: /* @__PURE__ */ p("div", {
35
+ children: /* @__PURE__ */ m("div", {
35
36
  className: "flex items-start gap-4",
36
- children: [/* @__PURE__ */ f(o, { icon: /* @__PURE__ */ f(u, { className: "h-5 w-5" }) }), /* @__PURE__ */ p("div", {
37
+ children: [/* @__PURE__ */ p(s, { icon: /* @__PURE__ */ p(d, { className: "h-5 w-5" }) }), /* @__PURE__ */ m("div", {
37
38
  className: "min-w-0 flex-1 space-y-3",
38
39
  children: [
39
- /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("h3", {
40
+ /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("h3", {
40
41
  className: "text-sm font-semibold text-text-primary",
41
- children: "Allow AI to read my health data"
42
- }), /* @__PURE__ */ f("p", {
42
+ children: v("mybodyshield.pages.settings.allowAi", "Allow AI to read my health data")
43
+ }), /* @__PURE__ */ p("p", {
43
44
  className: "text-sm text-text-secondary",
44
- children: "When on, AI can generate plain-language summaries from your logged training, nutrition, recovery and body-metric data. Rule-based guidance works either way."
45
+ children: v("mybodyshield.pages.settings.aiDetail", "When on, AI can generate plain-language summaries from your logged training, nutrition, recovery and body-metric data. Rule-based guidance works either way.")
45
46
  })] }),
46
- /* @__PURE__ */ p("div", {
47
+ /* @__PURE__ */ m("div", {
47
48
  className: "flex items-center justify-between gap-4 rounded-xl bg-bg-sunken/60 p-3 ring-1 ring-inset ring-border-seam/40",
48
- children: [/* @__PURE__ */ p("div", {
49
+ children: [/* @__PURE__ */ m("div", {
49
50
  className: "flex items-center gap-2",
50
- children: [/* @__PURE__ */ f(l, { className: "h-4 w-4 text-status-success-text" }), /* @__PURE__ */ f("span", {
51
+ children: [/* @__PURE__ */ p(u, { className: "h-4 w-4 text-status-success-text" }), /* @__PURE__ */ p("span", {
51
52
  className: "text-sm text-text-secondary",
52
- children: y ? "AI processing is enabled" : "AI processing is withdrawn"
53
+ children: x ? v("mybodyshield.pages.settings.aiEnabled", "AI processing is enabled") : v("mybodyshield.pages.settings.aiWithdrawn", "AI processing is withdrawn")
53
54
  })]
54
- }), /* @__PURE__ */ f(g, {
55
- checked: y,
56
- onCheckedChange: (e) => v.mutate(e),
57
- disabled: v.isPending,
58
- "aria-label": "Allow AI to read my health data"
55
+ }), /* @__PURE__ */ p(_, {
56
+ checked: x,
57
+ onCheckedChange: (e) => b.mutate(e),
58
+ disabled: b.isPending,
59
+ "aria-label": v("mybodyshield.pages.settings.allowAi", "Allow AI to read my health data")
59
60
  })]
60
61
  }),
61
- _.data?.acceptedAt && /* @__PURE__ */ p("p", {
62
+ y.data?.acceptedAt && /* @__PURE__ */ m("p", {
62
63
  className: "text-xs text-text-tertiary",
63
- children: [
64
- "Consent accepted",
65
- " ",
66
- new Date(_.data.acceptedAt).toLocaleDateString(void 0, { dateStyle: "medium" }),
67
- _.data.withdrawnAt && /* @__PURE__ */ p(d, { children: [
68
- " ",
69
- "· withdrawn",
70
- " ",
71
- new Date(_.data.withdrawnAt).toLocaleDateString(void 0, { dateStyle: "medium" })
72
- ] })
73
- ]
64
+ children: [v("mybodyshield.common.consentAccepted", "Consent accepted", { date: new Date(y.data.acceptedAt).toLocaleDateString(void 0, { dateStyle: "medium" }) }), y.data.withdrawnAt && /* @__PURE__ */ m(f, { children: [" ", v("mybodyshield.common.consentWithdrawn", "· withdrawn", { date: new Date(y.data.withdrawnAt).toLocaleDateString(void 0, { dateStyle: "medium" }) })] })]
74
65
  }),
75
- /* @__PURE__ */ f("div", {
66
+ /* @__PURE__ */ p("div", {
76
67
  className: "flex flex-wrap items-center gap-3 pt-1",
77
- children: /* @__PURE__ */ f(m, {
68
+ children: /* @__PURE__ */ p(h, {
78
69
  variant: "outline",
79
70
  size: "sm",
80
71
  asChild: !0,
81
- children: /* @__PURE__ */ p("a", {
72
+ children: /* @__PURE__ */ m("a", {
82
73
  href: "https://mybodyshield.com/ai-policy",
83
74
  target: "_blank",
84
75
  rel: "noopener noreferrer",
85
76
  className: "inline-flex items-center gap-1.5",
86
77
  children: [
87
- /* @__PURE__ */ f(c, { className: "h-4 w-4" }),
88
- "Read the AI Policy",
89
- /* @__PURE__ */ f(s, { className: "h-3.5 w-3.5 opacity-70" })
78
+ /* @__PURE__ */ p(l, { className: "h-4 w-4" }),
79
+ v("mybodyshield.common.aiPolicy", "Read the AI Policy"),
80
+ /* @__PURE__ */ p(c, { className: "h-3.5 w-3.5 opacity-70" })
90
81
  ]
91
82
  })
92
83
  })
@@ -99,6 +90,6 @@ function _() {
99
90
  });
100
91
  }
101
92
  //#endregion
102
- export { _ as SettingsPage };
93
+ export { v as SettingsPage };
103
94
 
104
95
  //# sourceMappingURL=SettingsPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SettingsPage.js","names":[],"sources":["../../../src/mybodyshield/pages/SettingsPage.tsx"],"sourcesContent":["/**\n * MyBodyShield Settings — AI-processing consent and related controls.\n * @module mybodyshield/pages\n *\n * BOFF-3838: exposes the AI-processing consent toggle inside the product so\n * members can enable or withdraw consent without leaving the app. Rule-based\n * guidance and manual logging continue to work when consent is withdrawn.\n */\n\nimport { Sparkles, ShieldCheck, ExternalLink, FileText } from 'lucide-react';\nimport { GlassCard, Switch, Button } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { LoadingView, ErrorView, Section } from '../components/StateViews';\nimport { useMybAiProcessingConsent, useMybSetAiProcessingConsent } from '../hooks';\nimport { IconChip } from './_shared/visuals';\n\nexport function SettingsPage() {\n const consent = useMybAiProcessingConsent();\n const setConsent = useMybSetAiProcessingConsent();\n\n if (consent.isLoading) {\n return (\n <PageLayout title=\"Settings\">\n <LoadingView label=\"Loading settings…\" />\n </PageLayout>\n );\n }\n\n if (consent.isError) {\n return (\n <PageLayout title=\"Settings\">\n <ErrorView\n title=\"Could not load settings\"\n message={consent.error instanceof Error ? consent.error.message : 'Unknown error'}\n onRetry={() => consent.refetch()}\n />\n </PageLayout>\n );\n }\n\n const enabled = consent.data?.enabled ?? false;\n\n return (\n <PageLayout\n title=\"Settings\"\n description=\"Manage MyBodyShield preferences and AI-processing consent.\"\n >\n <Section\n title=\"AI processing\"\n description=\"Control whether AI can read your health data to generate summaries and suggestions.\"\n >\n <GlassCard treatment=\"glass\" className=\"p-5\">\n <div className=\"flex items-start gap-4\">\n <IconChip icon={<Sparkles className=\"h-5 w-5\" />} />\n <div className=\"min-w-0 flex-1 space-y-3\">\n <div>\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Allow AI to read my health data\n </h3>\n <p className=\"text-sm text-text-secondary\">\n When on, AI can generate plain-language summaries from your logged training,\n nutrition, recovery and body-metric data. Rule-based guidance works either way.\n </p>\n </div>\n\n <div className=\"flex items-center justify-between gap-4 rounded-xl bg-bg-sunken/60 p-3 ring-1 ring-inset ring-border-seam/40\">\n <div className=\"flex items-center gap-2\">\n <ShieldCheck className=\"h-4 w-4 text-status-success-text\" />\n <span className=\"text-sm text-text-secondary\">\n {enabled ? 'AI processing is enabled' : 'AI processing is withdrawn'}\n </span>\n </div>\n <Switch\n checked={enabled}\n onCheckedChange={(checked) => setConsent.mutate(checked)}\n disabled={setConsent.isPending}\n aria-label=\"Allow AI to read my health data\"\n />\n </div>\n\n {consent.data?.acceptedAt && (\n <p className=\"text-xs text-text-tertiary\">\n Consent accepted{' '}\n {new Date(consent.data.acceptedAt).toLocaleDateString(undefined, {\n dateStyle: 'medium',\n })}\n {consent.data.withdrawnAt && (\n <>\n {' '}\n · withdrawn{' '}\n {new Date(consent.data.withdrawnAt).toLocaleDateString(undefined, {\n dateStyle: 'medium',\n })}\n </>\n )}\n </p>\n )}\n\n <div className=\"flex flex-wrap items-center gap-3 pt-1\">\n <Button variant=\"outline\" size=\"sm\" asChild>\n <a\n href=\"https://mybodyshield.com/ai-policy\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1.5\"\n >\n <FileText className=\"h-4 w-4\" />\n Read the AI Policy\n <ExternalLink className=\"h-3.5 w-3.5 opacity-70\" />\n </a>\n </Button>\n </div>\n </div>\n </div>\n </GlassCard>\n </Section>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;AAgBA,SAAgB,IAAe;CAC7B,IAAM,IAAU,GAA2B,EACrC,IAAa,GAA8B;AAEjD,KAAI,EAAQ,UACV,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAa,OAAM,qBAAsB,CAAA;EAC9B,CAAA;AAIjB,KAAI,EAAQ,QACV,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GACE,OAAM;GACN,SAAS,EAAQ,iBAAiB,QAAQ,EAAQ,MAAM,UAAU;GAClE,eAAe,EAAQ,SAAS;GAChC,CAAA;EACS,CAAA;CAIjB,IAAM,IAAU,EAAQ,MAAM,WAAW;AAEzC,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;YAEZ,kBAAC,GAAD;GACE,OAAM;GACN,aAAY;aAEZ,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cACrC,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAU,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAI,CAAA,EACpD,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;kBAA0C;QAEnD,CAAA,EACL,kBAAC,KAAD;QAAG,WAAU;kBAA8B;QAGvC,CAAA,CACA,EAAA,CAAA;OAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD,EAAa,WAAU,oCAAqC,CAAA,EAC5D,kBAAC,QAAD;UAAM,WAAU;oBACb,IAAU,6BAA6B;UACnC,CAAA,CACH;YACN,kBAAC,GAAD;SACE,SAAS;SACT,kBAAkB,MAAY,EAAW,OAAO,EAAQ;SACxD,UAAU,EAAW;SACrB,cAAW;SACX,CAAA,CACE;;OAEL,EAAQ,MAAM,cACb,kBAAC,KAAD;QAAG,WAAU;kBAAb;SAA0C;SACvB;SAChB,IAAI,KAAK,EAAQ,KAAK,WAAW,CAAC,mBAAmB,KAAA,GAAW,EAC/D,WAAW,UACZ,CAAC;SACD,EAAQ,KAAK,eACZ,kBAAA,GAAA,EAAA,UAAA;UACG;UAAI;UACO;UACX,IAAI,KAAK,EAAQ,KAAK,YAAY,CAAC,mBAAmB,KAAA,GAAW,EAChE,WAAW,UACZ,CAAC;UACD,EAAA,CAAA;SAEH;;OAGN,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD;SAAQ,SAAQ;SAAU,MAAK;SAAK,SAAA;mBAClC,kBAAC,KAAD;UACE,MAAK;UACL,QAAO;UACP,KAAI;UACJ,WAAU;oBAJZ;WAME,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;;WAEhC,kBAAC,GAAD,EAAc,WAAU,0BAA2B,CAAA;WACjD;;SACG,CAAA;QACL,CAAA;OACF;QACF;;IACI,CAAA;GACJ,CAAA;EACC,CAAA"}
1
+ {"version":3,"file":"SettingsPage.js","names":[],"sources":["../../../src/mybodyshield/pages/SettingsPage.tsx"],"sourcesContent":["/**\n * MyBodyShield Settings — AI-processing consent and related controls.\n * @module mybodyshield/pages\n *\n * BOFF-3838: exposes the AI-processing consent toggle inside the product so\n * members can enable or withdraw consent without leaving the app. Rule-based\n * guidance and manual logging continue to work when consent is withdrawn.\n */\n\nimport { Sparkles, ShieldCheck, ExternalLink, FileText } from 'lucide-react';\nimport { GlassCard, Switch, Button } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { LoadingView, ErrorView, Section } from '../components/StateViews';\nimport { useMybAiProcessingConsent, useMybSetAiProcessingConsent, useTr } from '../hooks';\nimport { IconChip } from './_shared/visuals';\n\nexport function SettingsPage() {\n const t = useTr();\n const consent = useMybAiProcessingConsent();\n const setConsent = useMybSetAiProcessingConsent();\n\n if (consent.isLoading) {\n return (\n <PageLayout title={t('mybodyshield.pages.settings.title', 'Settings')}>\n <LoadingView label={t('mybodyshield.pages.settings.loading', 'Loading settings…')} />\n </PageLayout>\n );\n }\n\n if (consent.isError) {\n return (\n <PageLayout title={t('mybodyshield.pages.settings.title', 'Settings')}>\n <ErrorView\n title={t('mybodyshield.pages.settings.errorTitle', 'Could not load settings')}\n message={\n consent.error instanceof Error\n ? consent.error.message\n : t('mybodyshield.common.unknownError', 'Unknown error')\n }\n onRetry={() => consent.refetch()}\n />\n </PageLayout>\n );\n }\n\n const enabled = consent.data?.enabled ?? false;\n\n return (\n <PageLayout\n title={t('mybodyshield.pages.settings.title', 'Settings')}\n description={t(\n 'mybodyshield.pages.settings.description',\n 'Manage MyBodyShield preferences and AI-processing consent.'\n )}\n >\n <Section\n title={t('mybodyshield.pages.settings.aiProcessing', 'AI processing')}\n description={t(\n 'mybodyshield.pages.settings.aiProcessingDescription',\n 'Control whether AI can read your health data to generate summaries and suggestions.'\n )}\n >\n <GlassCard treatment=\"glass\" className=\"p-5\">\n <div className=\"flex items-start gap-4\">\n <IconChip icon={<Sparkles className=\"h-5 w-5\" />} />\n <div className=\"min-w-0 flex-1 space-y-3\">\n <div>\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {t('mybodyshield.pages.settings.allowAi', 'Allow AI to read my health data')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {t(\n 'mybodyshield.pages.settings.aiDetail',\n 'When on, AI can generate plain-language summaries from your logged training, nutrition, recovery and body-metric data. Rule-based guidance works either way.'\n )}\n </p>\n </div>\n\n <div className=\"flex items-center justify-between gap-4 rounded-xl bg-bg-sunken/60 p-3 ring-1 ring-inset ring-border-seam/40\">\n <div className=\"flex items-center gap-2\">\n <ShieldCheck className=\"h-4 w-4 text-status-success-text\" />\n <span className=\"text-sm text-text-secondary\">\n {enabled\n ? t('mybodyshield.pages.settings.aiEnabled', 'AI processing is enabled')\n : t('mybodyshield.pages.settings.aiWithdrawn', 'AI processing is withdrawn')}\n </span>\n </div>\n <Switch\n checked={enabled}\n onCheckedChange={(checked) => setConsent.mutate(checked)}\n disabled={setConsent.isPending}\n aria-label={t(\n 'mybodyshield.pages.settings.allowAi',\n 'Allow AI to read my health data'\n )}\n />\n </div>\n\n {consent.data?.acceptedAt && (\n <p className=\"text-xs text-text-tertiary\">\n {t('mybodyshield.common.consentAccepted', 'Consent accepted', {\n date: new Date(consent.data.acceptedAt).toLocaleDateString(undefined, {\n dateStyle: 'medium',\n }),\n })}\n {consent.data.withdrawnAt && (\n <>\n {' '}\n {t('mybodyshield.common.consentWithdrawn', '· withdrawn', {\n date: new Date(consent.data.withdrawnAt).toLocaleDateString(undefined, {\n dateStyle: 'medium',\n }),\n })}\n </>\n )}\n </p>\n )}\n\n <div className=\"flex flex-wrap items-center gap-3 pt-1\">\n <Button variant=\"outline\" size=\"sm\" asChild>\n <a\n href=\"https://mybodyshield.com/ai-policy\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1.5\"\n >\n <FileText className=\"h-4 w-4\" />\n {t('mybodyshield.common.aiPolicy', 'Read the AI Policy')}\n <ExternalLink className=\"h-3.5 w-3.5 opacity-70\" />\n </a>\n </Button>\n </div>\n </div>\n </div>\n </GlassCard>\n </Section>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;AAgBA,SAAgB,IAAe;CAC7B,IAAM,IAAI,GAAO,EACX,IAAU,GAA2B,EACrC,IAAa,GAA8B;AAEjD,KAAI,EAAQ,UACV,QACE,kBAAC,GAAD;EAAY,OAAO,EAAE,qCAAqC,WAAW;YACnE,kBAAC,GAAD,EAAa,OAAO,EAAE,uCAAuC,oBAAoB,EAAI,CAAA;EAC1E,CAAA;AAIjB,KAAI,EAAQ,QACV,QACE,kBAAC,GAAD;EAAY,OAAO,EAAE,qCAAqC,WAAW;YACnE,kBAAC,GAAD;GACE,OAAO,EAAE,0CAA0C,0BAA0B;GAC7E,SACE,EAAQ,iBAAiB,QACrB,EAAQ,MAAM,UACd,EAAE,oCAAoC,gBAAgB;GAE5D,eAAe,EAAQ,SAAS;GAChC,CAAA;EACS,CAAA;CAIjB,IAAM,IAAU,EAAQ,MAAM,WAAW;AAEzC,QACE,kBAAC,GAAD;EACE,OAAO,EAAE,qCAAqC,WAAW;EACzD,aAAa,EACX,2CACA,6DACD;YAED,kBAAC,GAAD;GACE,OAAO,EAAE,4CAA4C,gBAAgB;GACrE,aAAa,EACX,uDACA,sFACD;aAED,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cACrC,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAU,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAI,CAAA,EACpD,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAE,uCAAuC,kCAAkC;QACzE,CAAA,EACL,kBAAC,KAAD;QAAG,WAAU;kBACV,EACC,wCACA,+JACD;QACC,CAAA,CACA,EAAA,CAAA;OAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD,EAAa,WAAU,oCAAqC,CAAA,EAC5D,kBAAC,QAAD;UAAM,WAAU;oBACb,IACG,EAAE,yCAAyC,2BAA2B,GACtE,EAAE,2CAA2C,6BAA6B;UACzE,CAAA,CACH;YACN,kBAAC,GAAD;SACE,SAAS;SACT,kBAAkB,MAAY,EAAW,OAAO,EAAQ;SACxD,UAAU,EAAW;SACrB,cAAY,EACV,uCACA,kCACD;SACD,CAAA,CACE;;OAEL,EAAQ,MAAM,cACb,kBAAC,KAAD;QAAG,WAAU;kBAAb,CACG,EAAE,uCAAuC,oBAAoB,EAC5D,MAAM,IAAI,KAAK,EAAQ,KAAK,WAAW,CAAC,mBAAmB,KAAA,GAAW,EACpE,WAAW,UACZ,CAAC,EACH,CAAC,EACD,EAAQ,KAAK,eACZ,kBAAA,GAAA,EAAA,UAAA,CACG,KACA,EAAE,wCAAwC,eAAe,EACxD,MAAM,IAAI,KAAK,EAAQ,KAAK,YAAY,CAAC,mBAAmB,KAAA,GAAW,EACrE,WAAW,UACZ,CAAC,EACH,CAAC,CACD,EAAA,CAAA,CAEH;;OAGN,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD;SAAQ,SAAQ;SAAU,MAAK;SAAK,SAAA;mBAClC,kBAAC,KAAD;UACE,MAAK;UACL,QAAO;UACP,KAAI;UACJ,WAAU;oBAJZ;WAME,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;WAC/B,EAAE,gCAAgC,qBAAqB;WACxD,kBAAC,GAAD,EAAc,WAAU,0BAA2B,CAAA;WACjD;;SACG,CAAA;QACL,CAAA;OACF;QACF;;IACI,CAAA;GACJ,CAAA;EACC,CAAA"}
@@ -53,7 +53,8 @@ export declare const METRIC_META: Record<MybBodyMetricType, {
53
53
  export declare const RECOVERY_META: Record<MybRecoveryMetric, {
54
54
  icon: typeof Gauge;
55
55
  tint: string;
56
- label: string;
56
+ labelKey: string;
57
+ labelFallback: string;
57
58
  }>;
58
59
  /** Circular icon chip with a soft tinted background. */
59
60
  export declare const IconChip: FC<{
@@ -1 +1 @@
1
- {"version":3,"file":"visuals.d.ts","sourceRoot":"","sources":["../../../../src/mybodyshield/pages/_shared/visuals.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,EACL,QAAQ,EAER,KAAK,EAKL,MAAM,EAQN,MAAM,EAIN,KAAK,EAEN,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EACV,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,cAAc,EACf,MAAM,aAAa,CAAC;AAMrB,eAAO,MAAM,KAAK,EAAE,EAAE,CAAC;IACrB,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAiBA,CAAC;AAMF,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CA6CA,CAAC;AAMF,eAAO,MAAM,SAAS;;;;CAIZ,CAAC;AAEX,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAaA,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CASlD,CAAC;AA+BF,eAAO,MAAM,iBAAiB;UARiC,OAAO,QAAQ;UAAQ,MAAM;EAQL,CAAC;AAWxF,eAAO,MAAM,WAAW;UATiC,MAAM;EASa,CAAC;AAU7E,eAAO,MAAM,cAAc;UAR8B,OAAO,MAAM;UAAQ,MAAM;EAQI,CAAC;AAezF,eAAO,MAAM,WAAW;UAXd,OAAO,MAAM;UAAQ,MAAM;UAAQ,MAAM;EAWsB,CAAC;AAY1E,eAAO,MAAM,aAAa;UARhB,OAAO,KAAK;UAAQ,MAAM;WAAS,MAAM;EAQ6B,CAAC;AAMjF,wDAAwD;AACxD,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAW/E,CAAC;AAEF,mEAAmE;AACnE,eAAO,MAAM,SAAS,EAAE,EAAE,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,IAAI,GAAG,MAAM,CAAA;CAAE,CAwBzF,CAAC;AAEF,+EAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,CAKT;AAED,OAAO,EAAE,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"visuals.d.ts","sourceRoot":"","sources":["../../../../src/mybodyshield/pages/_shared/visuals.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,EACL,QAAQ,EAER,KAAK,EAKL,MAAM,EAQN,MAAM,EAIN,KAAK,EAEN,MAAM,cAAc,CAAC;AAGtB,OAAO,KAAK,EACV,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,cAAc,EACf,MAAM,aAAa,CAAC;AAMrB,eAAO,MAAM,KAAK,EAAE,EAAE,CAAC;IACrB,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAiBA,CAAC;AAMF,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CA6CA,CAAC;AAMF,eAAO,MAAM,SAAS;;;;CAIZ,CAAC;AAEX,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAaA,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAiBlD,CAAC;AA+BF,eAAO,MAAM,iBAAiB;UARiC,OAAO,QAAQ;UAAQ,MAAM;EAQL,CAAC;AAWxF,eAAO,MAAM,WAAW;UATiC,MAAM;EASa,CAAC;AAU7E,eAAO,MAAM,cAAc;UAR8B,OAAO,MAAM;UAAQ,MAAM;EAQI,CAAC;AAezF,eAAO,MAAM,WAAW;UAXd,OAAO,MAAM;UAAQ,MAAM;UAAQ,MAAM;EAWsB,CAAC;AAqC1E,eAAO,MAAM,aAAa;UAjChB,OAAO,KAAK;UAAQ,MAAM;cAAY,MAAM;mBAAiB,MAAM;EAiCG,CAAC;AAMjF,wDAAwD;AACxD,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAW/E,CAAC;AAEF,mEAAmE;AACnE,eAAO,MAAM,SAAS,EAAE,EAAE,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,IAAI,GAAG,MAAM,CAAA;CAAE,CAwBzF,CAAC;AAEF,+EAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,CAKT;AAED,OAAO,EAAE,KAAK,EAAE,CAAC"}