+ {t("onboarding.v2.stepOf", { current: step, total: TOTAL_STEPS })}
+
- {/* Step 1: Profile */}
+ {/* ───── Step 1 — about you ───── */}
{step === 1 && (
-
-
{t("settings.profile")}
-
-
- setHeightCm(e.target.value)}
- placeholder="175"
- min={50}
- max={300}
- step={0.1}
- />
-
-
-
-
+ {t("onboarding.v2.step1.title")}
+
+
+ {t("onboarding.v2.step1.description")}
+
+
+
+
+
+
setDisplayName(e.target.value)}
+ autoComplete="nickname"
+ placeholder={t("settings.username")}
+ maxLength={50}
+ />
- {t("settings.genderHint")}
+ {t("onboarding.v2.step1.displayNameHint")}
+
+
+
+
+
+ {t("onboarding.v2.step1.languageHint")}
+
+
+
+
+
+
+ setHeightCm(e.target.value)}
+ placeholder="175"
+ min={50}
+ max={300}
+ step={0.1}
+ autoComplete="off"
+ />
+
+
+
+
+
+
+
-
+
)}
- {/* Step 2: Medications */}
+ {/* ───── Step 2 — first measurement ───── */}
{step === 2 && (
-
-
- {t("onboarding.medicationsTitle")}
-
-
- {t("onboarding.medicationsDescription")}
-
-
- setMedName(e.target.value)}
- placeholder={t("onboarding.medNamePlaceholder")}
- className="flex-1"
- />
- setMedDose(e.target.value)}
- placeholder={t("onboarding.medDosePlaceholder")}
- className="w-24"
- />
-
-
- {meds.length > 0 && (
- <>
-
- {meds.map((m, i) => (
- -
-
- {m.name} {m.dose && `(${m.dose})`}
-
-
-
- ))}
-
-
- {t("onboarding.medScheduleHint")}
-
- >
- )}
-
- )}
-
- {/* Step 3: Notifications teaser */}
- {step === 3 && (
-
-
- {t("onboarding.notificationsTitle")}
-
-
- {t("onboarding.notificationsDescription")}
-
-
-
- 📱 Telegram — {t("onboarding.telegramHint")}
+ {t("onboarding.v2.step2.title")}
+
+
+ {t("onboarding.v2.step2.description")}
-
- 🔔 Web Push — {t("onboarding.webPushHint")}
-
-
-
- {t("onboarding.notificationsLater")}
-
-
- )}
+
- {/* Step 4: Targets */}
- {step === 4 && (
-
-
- {t("onboarding.targetsTitle")}
-
-
- {t("onboarding.targetsDescription")}
-
-
-
-
-
setTargetSys(e.target.value)}
- placeholder="130"
- />
+ {measurementLogged ? (
+
+
+
{t("onboarding.v2.step2.added")}
-
-
- setTargetDia(e.target.value)}
- placeholder="85"
- />
-
-
-
-
- setTargetWeight(e.target.value)}
- placeholder="75"
- step={0.1}
- />
-
-
- )}
-
- {/* Navigation buttons */}
-
-
-
-
+
+ {t("onboarding.v2.step2.skipHint")}
+
+
+ )}
+
+ {/* ───── Step 3 — pick a channel ───── */}
+ {step === 3 && (
+
+ )}
+
+ {/* ───── Navigation row ───── */}
+
+ {step > 1 ? (
+
+ ) : (
+
+ )}
+
{step < TOTAL_STEPS ? (
-
+ {t("onboarding.v2.continue")}
+
+
) : (
-
+ {saving &&
}
+ {channelChoice === "NONE"
+ ? t("onboarding.v2.finish")
+ : t("onboarding.v2.step3.openSettings")}
+
)}
+
+ {/* Single Skip per step. Step 3's "NONE" choice already covers
+ skipping notifications, so we hide the underline on step 3. */}
+ {step < TOTAL_STEPS ? (
+
+ ) : null}
);
}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 6290f6843..ec9f8b2cb 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -40,6 +40,8 @@ import {
import { MeasurementForm } from "@/components/measurements/measurement-form";
import { MoodForm } from "@/components/mood/mood-form";
import { TrendCard } from "@/components/charts/trend-card";
+import { TrendHint } from "@/components/charts/trend-hint";
+import { GettingStartedChecklist } from "@/components/onboarding/getting-started-checklist";
const HealthChart = dynamic(
() =>
@@ -381,6 +383,12 @@ export default function DashboardPage() {
+ {/* v1.4: Getting-started checklist for brand-new users.
+ * Self-gates visibility on (onboardingCompletedAt == null
+ * || measurementCount < 5) and disappears once dismissed
+ * or fully complete. See B2 in the v1.4 discovery summary. */}
+
+
{/* Quick Entry Dialogs */}
- {charts.map((entry) => entry.node)}
+ {charts.map((entry) => (
+
+ ))}
>
);
})()}
diff --git a/src/components/charts/__tests__/trend-hint.test.tsx b/src/components/charts/__tests__/trend-hint.test.tsx
new file mode 100644
index 000000000..9d677dedf
--- /dev/null
+++ b/src/components/charts/__tests__/trend-hint.test.tsx
@@ -0,0 +1,39 @@
+import { describe, it, expect } from "vitest";
+import { renderToStaticMarkup } from "react-dom/server";
+
+import { TrendHint } from "../trend-hint";
+import { I18nProvider } from "@/lib/i18n/context";
+
+function render(node: React.ReactNode, locale: "en" | "de" = "en") {
+ return renderToStaticMarkup(
+